This commit is contained in:
Vladimir Khaidanov 2024-06-08 21:39:09 +03:00
commit 29b0987f86
2025 changed files with 358610 additions and 0 deletions

33
.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
#Ignore thumbnails created by Windows
Thumbs.db
#Ignore files built by Visual Studio
*.obj
*.exe
*.pdb
*.user
*.aps
*.pch
*.vspscc
*_i.c
*_p.c
*.ncb
*.suo
*.tlb
*.tlh
*.bak
*.cache
*.ilk
*.log
[Bb]in
[Dd]ebug*/
*.lib
*.sbr
obj/
[Rr]elease*/
_ReSharper*/
[Tt]est[Rr]esult*
.vs/
#Nuget packages folder
packages/
bin

View File

@ -0,0 +1,13 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View File

@ -0,0 +1,81 @@
# Knoks
## Technologies ##
* Angular 5
* Webpack 2
* TypeScript 2.x.x
* TSLint - [rules descriptions](http://palantir.github.io/tslint/rules/)
* [Angular2 material](https://github.com/angular/material2)
* SCSS for styles
Help video cast [online example](https://www.youtube.com/watch?v=X_JH1gBJe2E&list=PLqHlAwsJRxANDZPGvgX4DQCtN1TTUCUxx).
## Installation ##
* Pre requirements: installed [NodeJS 8.x.x with NPM](https://nodejs.org)
* On local repository the NPM command: **npm install**
* Install [editorconfig](http://editorconfig.org/) for your editor.
## Development Points ##
### NPM task runners: ###
1. `npm start` - Start dev server
2. `npm run test` - run tests
3. `npm run build` - default build production version. Result folder: dist/{brand name}
Default values:
BRAND_NAME="LDI"
API_URL="http://localhost:52281/v1"
API_IDENTIFIER="138659EBEBBF408AA1282D46EBDFBDC7"
1. to pass BRAND_NAME parameter in build command use next: `npm run build -- --env.brand_name=BRAND_NAME`
2. to pass API_URL parameter in build command use next: `npm run build -- --env.api_url=URL`
3. to pass API_IDENTIFIER parameter in build command use next: `npm run build -- --env.api_id=IDENTIFIER`
4. for example `npm run build -- --env.brand_name=BRAND_NAME --env.api_url=URL --env.api_id=IDENTIFIER`
---
### Application entry points: ###
Entry point for application: `src/app/app.module.ts`
All declared components, global providers, global imports
---
### Routing: ###
Setup: `src/app/app.routing.ts`
---
### Components ###
Root components: `app.component`
Other components placed to `src/app/components` folder
---
### Component Structure ###
1. component class
1. Naming: `{componentn name}.component.ts`
2. should export class with decorator `@Component`
2. component template
1. Naming: `{componentn name}.component.html`
3. component styles (_optional_)
1. Naming: `{componentn name}.component.scss`
---
### Shared
In `app/shared` folder common parts for component, like models, services, resolvers, etc...
---
## License ##
**Copyright © Lendoit. All Rights Reserved.**

View File

@ -0,0 +1,7 @@
var path = require('path');
var _root = path.resolve(__dirname, '..');
function root(args) {
args = Array.prototype.slice.call(arguments, 0);
return path.join.apply(path, [_root].concat(args));
}
exports.root = root;

View File

@ -0,0 +1,25 @@
Error.stackTraceLimit = Infinity;
import 'core-js/es6';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/long-stack-trace-zone';
import 'zone.js/dist/proxy';
import 'zone.js/dist/sync-test';
import 'zone.js/dist/jasmine-patch';
import 'zone.js/dist/async-test';
import 'zone.js/dist/fake-async-test';
/* tslint:disable:no-var-requires typedef no-var-keyword */
var appContext = (<{ context?: Function }>require).context('../src', true, /\.spec\.ts/);
appContext.keys().forEach(appContext);
var testing = require('@angular/core/testing');
var browser = require('@angular/platform-browser-dynamic/testing');
/* tslint:enable:no-var-requires typedef no-var-keyword */
testing.TestBed.initTestEnvironment(browser.BrowserDynamicTestingModule, browser.platformBrowserDynamicTesting());

View File

@ -0,0 +1,42 @@
var webpackConfig = require('./webpack.test');
module.exports = function (config) {
var _config = {
basePath: '',
frameworks: ['jasmine'],
files: [
{ pattern: './config/karma-test-shim.ts', watched: false },
{ pattern: './node_modules/@angular/material/prebuilt-themes/indigo-pink.css' }
],
preprocessors: {
'./config/karma-test-shim.ts': ['webpack', 'sourcemap']
},
webpack: webpackConfig,
webpackMiddleware: {
stats: 'errors-only'
},
webpackServer: {
noInfo: true
},
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: false,
browsers: ['PhantomJS'],
singleRun: true,
plugings:[
require( 'karma-jasmine' ),
require( 'karma-phantomjs-launcher' ),
]
};
config.set(_config);
};

View File

@ -0,0 +1,103 @@
var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var CopyWebPackPlugin = require('copy-webpack-plugin');
var helpers = require('./helpers');
var path = require('path');
module.exports = {
entry: {
'polyfills': './src/polyfills.ts',
'vendor': './src/vendor.ts',
'app': './src/main.ts'
},
resolve: {
extensions: ['.js', '.ts']
},
module: {
rules: [
{
test: /\.ts$/,
enforce: 'pre',
loader: 'tslint-loader',
options: {
configuation: require('../tslint.json'),
emitErrors: true,
failOnHint: true
}
},
{
test: /\.ts$/,
use: ['awesome-typescript-loader', 'angular2-template-loader', 'angular-router-loader']
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'file-loader',
options: { name: 'assets/[name].[hash].[ext]' }
},
{
test: /\.css$/,
exclude: [helpers.root('src', 'modules')],
loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader?sourceMap' })
},
{
test: /\.css$/,
include: [helpers.root('src', 'modules')],
loader: 'raw-loader'
},
{
test: /\.scss$/,
exclude: [helpers.root('src', 'modules')],
loader: ExtractTextPlugin.extract({
fallback: 'style-loader', use: ['css-loader?sourceMap', 'sass-loader?sourceMap']
})
},
{
test: /\.scss$/,
include: [helpers.root('src', 'modules')],
use: ['raw-loader', {
loader: 'sass-loader',
// options: {
// importer: (url, prev, done) => {
// // if (url[0] === '~') {
// // console.log('inside', url,url.substr(1), path.resolve('node_modules', url.substr(1)));
// // url = path.resolve('node_modules', url.substr(1));
// // } else if (url.startsWith('styles/')) {
// // url = path.resolve('src/', url);
// // }
// return { file: url }
// }
// }
}]
},
]
},
plugins: [
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)(@angular|esm5)/, // /angular(\\|\/)core(\\|\/)@angular/,
helpers.root('src')
),
new webpack.optimize.CommonsChunkPlugin({
name: ['app', 'vendor', 'polyfills']
}),
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CopyWebPackPlugin([{
from: helpers.root('src', 'i18n'),
to: 'i18n'
}, {
from: helpers.root('favicons'),
to: 'favicons'
}])
]
};

View File

@ -0,0 +1,34 @@
var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');
const SETTINGS = {
API_URL: 'http://localhost:52281/v1',
API_IDENTIFIER: "138659EBEBBF408AA1282D46EBDFBDC7"
};
module.exports = webpackMerge(commonConfig, {
devtool: 'cheap-module-eval-source-map',
output: {
path: helpers.root('dist'),
publicPath: 'http://localhost:8080/',
filename: '[name].js',
chunkFilename: '[id].chunk.js'
},
plugins: [
new ExtractTextPlugin('[name].css'),
new webpack.DefinePlugin({
SETTINGS: JSON.stringify(SETTINGS)
})
],
devServer: {
historyApiFallback: true,
stats: 'minimal'
}
});

View File

@ -0,0 +1,54 @@
var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');
var rimraf = require('rimraf');
const ENV = process.env.NODE_ENV = process.env.ENV = 'production';
module.exports = function (env = {}) {
env = env || {};
const SETTINGS = {
API_URL: env.api_url || 'http://localhost:52281/v1',
API_IDENTIFIER: env.api_id || "138659EBEBBF408AA1282D46EBDFBDC7"
};
const BRAND_NAME = env.brand_name || 'KNK';
// wait for removing destination folder
rimraf.sync(helpers.root('dist', BRAND_NAME));
return webpackMerge(commonConfig, {
devtool: 'source-map',
output: {
path: helpers.root('dist', BRAND_NAME),
//publicPath: `/${BRAND_NAME}`,
publicPath: `/`,
filename: '[name].[hash].js',
chunkFilename: '[id].[hash].chunk.js'
},
plugins: [
new webpack.NoEmitOnErrorsPlugin(),
// new webpack.optimize.DedupePlugin(),
new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618
mangle: {
keep_fnames: true
}
}),
new ExtractTextPlugin('[name].[hash].css'),
new webpack.DefinePlugin({
'process.env': {
'ENV': JSON.stringify(ENV)
},
'SETTINGS': JSON.stringify(SETTINGS)
}),
new webpack.LoaderOptionsPlugin({
htmlLoader: {
minimize: false // workaround for ng2
}
})
]
});
};

View File

@ -0,0 +1,67 @@
var webpack = require('webpack');
//var webpackMerge = require('webpack-merge');
//var ExtractTextPlugin = require('extract-text-webpack-plugin');
//var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');
const SETTINGS = {
API_URL: 'http://localhost:52281/v1',
API_IDENTIFIER: "138659EBEBBF408AA1282D46EBDFBDC7"
};
module.exports = {
devtool: 'inline-source-map',
resolve: {
extensions: [ '.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loaders: ['awesome-typescript-loader', 'angular2-template-loader']
},
{
test: /\.html$/,
loader: 'html-loader'
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
loader: 'null-loader'
},
{
test: /\.css$/,
exclude: helpers.root('src', 'modules'),
loader: 'null-loader'
},
{
test: /\.css$/,
include: helpers.root('src', 'modules'),
loader: 'raw-loader'
},
{
test: /\.scss$/,
exclude: helpers.root('src', 'modules'),
loader: 'null-loader'
},
{
test: /\.scss$/,
include: helpers.root('src', 'modules'),
use: ['raw-loader','sass-loader']
}
]
},
plugins: [
new webpack.DefinePlugin({
SETTINGS: JSON.stringify(SETTINGS)
}),
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)(@angular|esm5)/, // /angular(\\|\/)core(\\|\/)@angular/,
helpers.root('src'),
{}
)
]
}

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,4 @@
/**
* Node.js entry for karma.
*/
module.exports = require('./config/karma.conf.js');

11992
Archive/FrontEnd.Angular5/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,75 @@
{
"name": "front-end",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "webpack-dev-server --inline --progress --port 8080 --open",
"test": "karma start",
"build": "webpack --config config/webpack.prod.js --profile --bail",
"lint": "ng lint",
"e2e": "ng e2e"
},
"license": "Knoks",
"dependencies": {
"@angular/animations": "^5.2.0",
"@angular/cdk": "^5.1.1",
"@angular/common": "^5.2.0",
"@angular/compiler": "^5.2.0",
"@angular/core": "^5.2.0",
"@angular/flex-layout": "^2.0.0-beta.12",
"@angular/forms": "^5.2.0",
"@angular/http": "^5.2.2",
"@angular/material": "2.0.0-beta.7",
"@angular/platform-browser": "^5.2.0",
"@angular/platform-browser-dynamic": "^5.2.0",
"@angular/router": "^5.2.0",
"@ngx-translate/core": "^9.1.1",
"@ngx-translate/http-loader": "^2.0.1",
"angular-router-loader": "^0.8.2",
"angular2-template-loader": "^0.6.2",
"awesome-typescript-loader": "^3.4.1",
"core-js": "^2.4.1",
"css-loader": "^0.28.9",
"hammerjs": "^2.0.8",
"html-loader": "^0.5.5",
"http-loader": "0.0.1",
"intl": "^1.2.5",
"ng2-cookies": "^1.0.12",
"node-sass": "^4.7.2",
"raw-loader": "^0.5.1",
"rxjs": "^5.5.6",
"sass-loader": "^6.0.6",
"style-loader": "^0.20.1",
"tslint-loader": "^3.5.3",
"zone.js": "^0.8.19"
},
"devDependencies": {
"@angular/language-service": "^5.2.0",
"@types/hammerjs": "^2.0.34",
"@types/jasmine": "~2.8.3",
"@types/jasminewd2": "~2.0.2",
"@types/node": "~6.0.60",
"codelyzer": "^4.0.1",
"copy-webpack-plugin": "^4.0.1",
"extract-text-webpack-plugin": "^2.1.2",
"html-webpack-plugin": "^2.29.0",
"jasmine-core": "~2.8.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~2.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "^1.2.1",
"karma-jasmine": "~1.1.0",
"karma-jasmine-html-reporter": "^0.2.2",
"karma-phantomjs-launcher": "^1.0.4",
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.9",
"null-loader": "^0.1.1",
"protractor": "~5.1.2",
"ts-node": "~4.1.0",
"tslint": "~5.9.1",
"typescript": "^2.5.3",
"webpack": "^2.7.0",
"webpack-dev-server": "^2.5.1",
"webpack-merge": "^4.1.0"
}
}

View File

@ -0,0 +1,15 @@
@import '../../src/scss/lendoit-theme';
body {
background: #fff;
margin: 0;
padding: 0;
font-family: $page-font-family;
}
@import '../../src/scss/common/font-sizes';
@import '../../src/scss/common/font-family';
.common-margin-top-bottom {
@include common-margin-top-bottom();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 176.5 53.2" style="enable-background:new 0 0 176.5 53.2;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{fill:#7CE0FC;}
</style>
<g>
<g>
<g>
<path class="st0" d="M46.8,36.7c-2.3,1.9-5.1,3.3-10.7,3.3c-9.2,0-13.9-5.2-13.9-15.2c0-9.5,5.3-15.1,13.7-15.1
c7.4,0,11.7,4.7,11.7,11.8c0,1.5-0.1,2.9-0.4,5.2H29.9c0.1,4.6,2.2,7.1,7.1,7.1c4.6,0,8.1-2.2,9.1-3.3L46.8,36.7z M30,21.5h10.3
v-0.8c0-3.4-1.4-5.2-4.6-5.2C32.2,15.5,30.1,18.1,30,21.5z"/>
</g>
<g>
<path class="st0" d="M77.9,39.4h-7.8V20.9c0-3-1.4-4.8-4-4.8c-2.3,0-4.1,1.3-5,3.5v19.8h-7.8V10.2h6.1l1.5,2.7h0.2
c1.8-2.1,4.8-3.5,7.9-3.5c5.7,0,8.9,3.2,8.9,9.7V39.4z"/>
</g>
<g>
<path class="st0" d="M143.5,10.2h7.8v29.2h-7.8V10.2z"/>
</g>
<g>
<path class="st0" d="M147.3,0c2.3,0,4,1.7,4,4.1c0,2.1-1.7,3.9-4,3.9c-2.2,0-3.9-1.7-3.9-3.9C143.4,1.7,145.1,0,147.3,0z"/>
</g>
<g>
<path class="st0" d="M159.1,30.1V16.2h-3.7v-5.5l3.8-0.5l0-10.2h6.7l0.7,10.2h5.4l1,5.9h-6.3l0.3,14.1c0,2,1.1,3,3,3
c1.2,0,2.8-0.4,4-1.3l2,5.8c-1,0.7-3.3,1.8-8.1,1.8C162.7,39.7,159.1,37.1,159.1,30.1z"/>
</g>
<g>
<path class="st0" d="M6.8,2l0.8,28.3c0,1.5,1.3,2.6,2.7,2.6h10v7.2H6.5c-3.5,0-6.4-3.8-6.4-7.4V2H6.8z"/>
</g>
<path class="st1" d="M119.1,27.9c-0.7,1.1-1.8,2.2-3.4,3.3c-1.1,0.7-2.3,1.5-3.4,2.1c-4.1,2.4-8.2,4.3-10.3,5.2
c-0.3,0.1-0.6,0.3-0.8,0.4c-0.1,0-0.1,0-0.2,0.1c-0.2,0.1-0.4,0.2-0.4,0.2c-1.9,0.8-4,1.2-6.2,1.2c-0.1,0-0.2,0-0.3,0
c-0.1,0-0.3,0-0.4,0c-6,0-10.5-4-10.5-13.3c0-12.1,6.4-16,12.5-16c2.7,0,3.5,0.3,4.6,0.6L100.8,0l6.9,0l0.1,14.3
c0,0.4-0.2,0.8-0.5,1c-1.3,0.8-2.8,1.7-4.1,2.6c-0.4,0.3-0.9,0.7-1.4,1c-0.6,0.5-1.4,0.3-1.7-0.4c-0.6-1.2-1.8-1.4-3.2-1.4
c0,0,0,0,0,0c-3.2,0-5.8,1.4-5.8,9.1c0,5.6,2,7.9,4.9,7.9l0.8,0c0.1,0,0.3,0,0.4,0c0.1,0,0.2,0,0.2,0c0.1,0,0.1,0,0.2,0
c0.1,0,0.2-0.1,0.3-0.1c0.1,0,0.1,0,0.2-0.1c0,0,0.1,0,0.2-0.1c2.6-1,10.1-5.6,10.8-6c1.2-0.7,1.9-2.2,2.2-2.8
c0.1-0.2,0.2-0.4,0.4-0.5c1.1-0.7,4.9-2.9,6.8-4c0.6-0.4,1.4-0.1,1.6,0.6C120.4,22.6,120.9,25.2,119.1,27.9z"/>
<path class="st1" d="M137.2,25.4c0,8.8-3.8,15-13.2,15c-6.2,0-9.1-1.2-10.2-5.2c-0.1-0.5,0.1-1,0.5-1.3c0.9-0.5,1.6-1,2.5-1.6
c0.7-0.5,1.4-1,1.9-1.5c0.6-0.6,1.7-0.2,1.8,0.6c0.4,2.3,1.3,2.9,3.8,2.9c3.9,0,5.1-2.8,5.1-8.7c0-5.8-1.4-8.9-5.2-9.1
c-0.1,0-0.2,0-0.3,0c-0.2,0-0.5,0-0.9,0.1c-0.5,0.1-0.9,0.2-1.3,0.4c0,0,0,0-0.1,0c-3.2,1.3-8.2,4.5-10.5,5.8
c-0.3,0.1-0.5,0.3-0.6,0.3c-1.6,0.8-1.9,2.2-2.2,2.9c-0.1,0.3-0.3,0.5-0.6,0.7l-6,3.4c-0.6,0.3-1.3,0-1.5-0.6
c-0.7-1.9-1.7-6,1.1-8.4c5.3-4.4,13.9-8.3,15.3-9c2-1,4.6-1.6,7.6-1.6C134.2,10.6,137.2,16.5,137.2,25.4z"/>
</g>
<g>
<path class="st0" d="M-0.2,45.8h1v6.6H4v0.8h-4.1V45.8z"/>
<path class="st0" d="M15.5,49.7h-2.9v2.7h3.2v0.8h-4.2v-7.4h4v0.8h-3v2.3h2.9V49.7z"/>
<path class="st0" d="M23.6,53.2v-7.4h1l2.4,3.8c0.5,0.9,1,1.6,1.3,2.4l0,0c-0.1-1-0.1-1.9-0.1-3v-3.1h0.9v7.4h-1l-2.4-3.8
c-0.5-0.8-1-1.7-1.4-2.5l0,0c0.1,0.9,0.1,1.8,0.1,3.1v3.2H23.6z"/>
<path class="st0" d="M37.5,45.9c0.6-0.1,1.3-0.2,2-0.2c1.4,0,2.4,0.3,3,0.9c0.7,0.6,1,1.5,1,2.7c0,1.2-0.4,2.2-1.1,2.9
c-0.7,0.7-1.8,1.1-3.3,1.1c-0.7,0-1.3,0-1.7-0.1V45.9z M38.4,52.4c0.2,0,0.6,0.1,1,0.1c2,0,3.2-1.1,3.2-3.1c0-1.7-1-2.9-3-2.9
c-0.5,0-0.9,0-1.1,0.1V52.4z"/>
<path class="st0" d="M52.4,45.8v7.4h-1v-7.4H52.4z"/>
<path class="st0" d="M60.6,53.2v-7.4h1l2.4,3.8c0.5,0.9,1,1.6,1.3,2.4l0,0c-0.1-1-0.1-1.9-0.1-3v-3.1h0.9v7.4h-1l-2.4-3.8
c-0.5-0.8-1-1.7-1.4-2.5l0,0c0.1,0.9,0.1,1.8,0.1,3.1v3.2H60.6z"/>
<path class="st0" d="M80.1,52.8c-0.4,0.2-1.3,0.4-2.3,0.4c-1.1,0-2-0.3-2.8-1c-0.6-0.6-1-1.6-1-2.8c0-2.2,1.5-3.8,4-3.8
c0.9,0,1.5,0.2,1.8,0.3l-0.2,0.8c-0.4-0.2-0.9-0.3-1.6-0.3c-1.8,0-3,1.1-3,3c0,1.9,1.1,3,2.9,3c0.6,0,1.1-0.1,1.3-0.2v-2.2h-1.5
v-0.8h2.4V52.8z"/>
<path class="st0" d="M98.1,46.6h-2.3v-0.8h5.5v0.8H99v6.6h-1V46.6z"/>
<path class="st0" d="M109.7,45.8v3.1h3.6v-3.1h1v7.4h-1v-3.5h-3.6v3.5h-1v-7.4H109.7z"/>
<path class="st0" d="M126.3,49.7h-2.9v2.7h3.2v0.8h-4.2v-7.4h4v0.8h-3v2.3h2.9V49.7z"/>
<path class="st0" d="M144.3,53.2l-1.9-7.4h1l0.9,3.8c0.2,0.9,0.4,1.8,0.5,2.6h0c0.1-0.7,0.4-1.6,0.6-2.6l1-3.7h1l0.9,3.8
c0.2,0.9,0.4,1.8,0.5,2.5h0c0.2-0.8,0.4-1.6,0.6-2.6l1-3.7h1l-2.1,7.4h-1l-0.9-3.9c-0.2-0.9-0.4-1.7-0.5-2.4h0
c-0.1,0.7-0.3,1.5-0.6,2.4l-1.1,3.9H144.3z"/>
<path class="st0" d="M159.5,50.8l-0.8,2.3h-1l2.5-7.4h1.2l2.5,7.4h-1l-0.8-2.3H159.5z M162,50.1l-0.7-2.1
c-0.2-0.5-0.3-0.9-0.4-1.4h0c-0.1,0.4-0.2,0.9-0.4,1.3l-0.7,2.1H162z"/>
<path class="st0" d="M172.4,53.2V50l-2.3-4.3h1.1l1,2c0.3,0.6,0.5,1,0.7,1.5h0c0.2-0.5,0.5-1,0.7-1.5l1.1-2h1.1l-2.5,4.3v3.2
H172.4z"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,86 @@
{
"[page not found]": "Page not found",
"[loanrequest.info]": "<Information about the responsibillity for the correct data>",
"[loanrequest.continue]": "Continue",
"[loandetails.hide]": "Hide details",
"[loandetails.edit]": "Edit",
"[loandetails.total to repay]": "Total to&nbsp;repay",
"[loandetails.including interest]": "Including interest",
"[loandetails.return date]": "Return date",
"[loandetails]": "Loan details",
"[loandetails.text.lg]": "{{sign.amount}} {{amount}} for {{period}} {{sign.period}} + {{sign.amount}} {{interest}} = <span class='total'>{{sign.amount}} {{total}}</span> total to repay",
"[loandetails.text.xs]": "<div class='text-xs'>for {{period}} {{sign.period}}</div><div class='text-xs'>interest</div><div class='text-xs'>total to repay</div>",
"[createaccount.resend pin]": "Resend PIN",
"[createaccount.change mobile]": "Change mobile",
"[createaccount.choose password]": "Choose password for your username (email) - {{email}}",
"[createaccount.sequrity questions]": "if you've forgotten your password or want to change personal details,<br/>we'll ask these questions to make sure it's really you",
"[loanconfirm.loan explanation]": "Loan explanation text",
"[loanconfirm.important info]": "Loan important info text",
"[loanconfirm.terms and conditions]": "Loan explanation text",
"[I've read the 3 documents]": "I've read the 3 documents and i'm ready to continue.",
"[approveterms.info]": "You must read the information below carefully and make sure that this loan is right for you.<br/>We'll also email it to you. If you need help <a href='mailto:contact@lendoit.com?subject=Lendoit - alpha version support'>contact@lendoit.com</a>",
"[transfermethod.title]": "Choose the way to take {{sign}} {{amount}}",
"[transfermethod.security-notation]": "<security notation text, addition information about the way and transaction period>",
"[transfermethod.credit card]": "Credit Card",
"[transfermethod.wire transfer]": "Wire Transfer",
"[transfermethod.alternative]": "Alternative",
"[transfermethod.ethereum]": "Ethereum",
"[transfermethod.credit card.header]": "Please enter all data (it can be not yours credit card):",
"[transfermethod.wire transfer.header]": "Please enter all data (it can be not yours account):",
"[transfermethod.alternative.header]": "Please choose the method you with to pay with:",
"[transfermethod.ethereum.header]": "Please enter your Ethereum address",
"[transfermethod.repaymentnotice]": "(we save card number, but you can change before the repayment)",
"[verification.title]": "Verification",
"[verification.info]": "The loan, based on your information, is almost ready. The only thing left is to finish verification.",
"[verification.info.please upload]": "Please upload all the documents bellow so we can send your loan application to verification and scoring providers.",
"[verification.add scan text]": "Add a scan or photograph of your {{propertyName}}",
"[verification.add button]": "Add",
"[verification.reload button]": "Reload",
"[verification.replace button]": "Replace",
"[verification.info.show condition]": "Show condition",
"[verification.info.hide condition]": "Hide condition",
"[loanrequest.edit loan details]": "Edit loan details",
"[verification.status button.approved]": "approved",
"[verification.status button.in progress]": "in progress",
"[cancel request]": "Cancel request",
"[cancel request dialog.are you sure]": "Are you sure you want to cancel your request?",
"[cancel request dialog.reason placeholder]": "Please tell us why",
"[cancel request dialog.yes cancel]": "YES, CANCEL",
"[cancel request dialog.no mistake]": "No, it's a mistake",
"[personal details attention text]": "Anyone aged 18 or above who is a UK resident and has a UK current account can lend at <system name> without restriction",
"[changemobile dialog.enter new mobile and we will resend you PIN]": "Enter new mobile and we will resend you PIN",
"[paging.previous]": "Previous",
"[paging.next]": "Next",
"[steps.preview]": "Previous step",
"[menu.logo]": "Knock",
"[menu.signin]": "Sign in",
"[menu.signout]": "Sign out",
"[menu.my account]": "My Account",
"[menu.how it]": "How it Works",
"[menu.about us]": "About Us",
"[menu.submenu.see your rates]": "See your rates",
"[menu.submenu.right for you]": "Is it right for you",
"[menu.submenu.complaints]": "Complaints policy",
"[menu.submenu.debt advice]": "Debt advice",
"[menu.newsletter.placeholder]": "Email for newsletter",
"[menu.newsletter.button]": "Sign up",
"[menu.newsletter.text]": "Only important and interesting topics",
"[footer.copy]": "© 2017 Lendoit All rights reserved",
"[footer.important topic]": "Only important and interesting topics",
"[property name.Make profile anonymous?]": "Make profile anonymous? (can reduce the chance to get loan)",
"[property name.Would you like to get our newsletter]": "Would you like to get our newsletter",
"[login.enter your account data]":"Enter your account data",
"[login.have not got account]": "Have not got account",
"[login.forgot password]":"Forgot password",
"[dashboard.help center]": "Help center",
"[myaccaunt.save]": "Save",
"[become a lender.title]": "This feature is reserved for the pre-sale participants",
"[become a lender.button]": "PRE-SALE WHITELIST",
"[user name]": "User name"
}

View File

@ -0,0 +1,4 @@
{
"[page not found]":"Страница не найдена",
"[user name]": "Пользователь"
}

View File

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<base href="/">
<title>Knoks</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/png" href="/favicons/icon-32.png" sizes="32x32">
<!--<link rel="icon" type="image/png" href="/favicons/icon-16.png" sizes="16x16">-->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,700" rel="stylesheet">
<style>
.loading::before,
.loading::after {
position: fixed;
z-index: 3000;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.loading::before {
content: '';
background-color: #fff;
}
.loading::after {
font-family: "Helvetica Neue", Helvetica, sans-serif;
content: 'LOADING';
text-align: center;
white-space: pre;
font-weight: normal;
font-size: 24px;
letter-spacing: 0.04rem;
color: #000;
opacity: 0.8;
animation: animation 1s alternate infinite;
}
@keyframes animation {
to {
opacity: 0.2;
}
}
</style>
</head>
<body>
<knk-app>
<div class="loading"> </div>
</knk-app>
</body>
</html>

View File

@ -0,0 +1,11 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './modules/app/app.module';
if (process.env.ENV === 'production') {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,16 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
export interface ICountryMock {
phonePrefix: string;
}
@Injectable()
export class CountryServiceMock {
get Country(): Observable<ICountryMock> {
return Observable.of({ phonePrefix: '7' });
}
}

View File

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { IMenuItem } from '../modules/core/shared/header/header.model';
@Injectable()
export class HeaderServiceMock {
getMenu(): IMenuItem[] {
const menu: IMenuItem[] = [];
menu.push({ name: 'how it', translateKey: '[menu.how it]', rout: '', isActive: false, isVisible: true });
menu.push({ name: 'about us', translateKey: '[menu.about us]', rout: '', isActive: false, isVisible: true });
return menu;
}
}

View File

@ -0,0 +1,6 @@
export * from './router.mock';
export * from './token.service.mock';
export * from './country.service.mock';
export * from './translateloader.mock';
export * from './header.service.mock';
export * from './user.service.mock';

View File

@ -0,0 +1,54 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
/* tslint:disable */
@Injectable()
export class ActivatedRouteMock {
// ActivatedRoute.params is Observable
private subject = new BehaviorSubject(this.testParams);
params = this.subject.asObservable();
// Test parameters
private _testParams: {};
get testParams() { return this._testParams; }
set testParams(params: {}) {
this._testParams = params;
this.subject.next(params);
}
private _queryParams: {}
get queryParams() { return this._queryParams; }
set queryParams(params: {}) {
this._queryParams = params;
this.subject.next(params);
}
// ActivatedRoute.snapshot.params
get snapshot() {
return {
params: this.testParams,
queryParams: this.queryParams,
};
}
}
@Injectable()
export class ActivatedRouteSnapshotMock {
params: any;
url: any;
queryParams: any;
fragment: any;
data: any;
outlet: any;
component: any;
routeConfig: any;
root: any;
parent: any;
firstChild: any;
children: any;
pathFromRoot: any;
paramMap: any;
queryParamMap: any;
}
/* tslint:enable */

View File

@ -0,0 +1,17 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
@Injectable()
export class TokenServiceMock {
public authorizationChanged: Observable<boolean>;
private authorizationChangeSource = new Subject<boolean>();
constructor() {
//console.log('token service ctor');
this.authorizationChanged = this.authorizationChangeSource.asObservable();
}
get authorization(): string {
return 'test mockup authorization';
}
}

View File

@ -0,0 +1,18 @@
import { TranslateLoader } from '@ngx-translate/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
export class TranslateLoaderMock implements TranslateLoader {
getTranslation(lang: string): Observable<any> {
if (lang === 'en') {
return Observable.of({ '[page not found]': 'page not found' });
}
if (lang === 'ru') {
return Observable.of({ '[page not found]': 'страница не найдена' });
}
return Observable.throw('not available language');
}
}

View File

@ -0,0 +1,40 @@
import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { IUser, IUserAccounts, IUserCredentials } from '../modules/core/shared/user/user.model';
import { IApiError, IApiResponseError, IApiResponseSuccess, isApiResponseError } from '../modules/core/shared/token/apiresponse.model';
@Injectable()
export class UserServiceMock {
user: IUser;
userAccounts: IUserAccounts;
testParam: string;
get UserCredentials(): IUserCredentials {
return this.user || this.userCredentials;
}
set UserCredentials(value: IUserCredentials) {
this.userCredentials = value;
}
private userCredentials: IUserCredentials;
constructor() { }
createUser(user: IUser): Observable<IUser> {
return new Observable<IUser>();
}
loadUser(): Observable<IUserAccounts> {
return new Observable<IUserAccounts>();
}
updateUser(user: IUser): Observable<IUser> {
return new Observable<IUser>();
}
private handleError(error: IApiError): Observable<IUser> {
return Observable.throw(error.msg || error);
}
}

View File

@ -0,0 +1,35 @@
<md-sidenav-container class="sidenav-container-wraper font-size-15px" ngClass.lt-md="lt-md">
<md-sidenav mode="over" opened="false" #mobilesidenav>
<span class="actions-row" fxLayout="row">
<span fxFlexLayout="start start" fxFlex="50"><i (click)="mobilesidenav.toggle()" class="material-icons">close</i></span>
<span class="action" fxFlexLayout="end start" *ngIf="!isLogin" fxFlex><a mdLine routerLink="en/login">{{'[menu.signin]'|translate}}</a></span>
<span class="action" fxFlexLayout="end start" *ngIf="isLogin" fxFlex><a mdLine (click)="logout()" routerLink="en/login">{{'[menu.signout]'|translate}}</a></span>
</span>
<md-divider></md-divider>
<div *ngFor="let item of sideMenu" class="menu-item general-item">
<div class="link-wraper"><a *ngIf="item.isVisible" mdLine routerLink="{{item.rout}}" [ngClass]="{'active' : item.isActive}" (click)="setActiveMenu(item)">{{item.translateKey|translate}}</a>
</div>
<div *ngFor="let subMenuItem of item.subMenu" class="menu-item submenu-item" fxLayout="row" fxLayoutWraps>
<div class="link-wraper"><a *ngIf="subMenuItem.isVisible" mdLine routerLink="{{subMenuItem.rout}}" [ngClass]="{'active' : subMenuItem.isActive}"
(click)="setActiveMenu(subMenuItem)">{{subMenuItem.translateKey|translate}}</a>
</div>
</div>
</div>
<div class="newsletter">
<div class="heading font-size-30px">{{'[menu.newsletter]'|translate}}</div>
<md-input-container floatPlaceholder="never">
<input mdInput type="email" [placeholder]="'[menu.newsletter.placeholder]'|translate" class="newsletter-input">
</md-input-container>
<div fxLayout="row"><button md-button color="accent" fxFlex fxLayout="row" fxLayoutAlign="center center">{{'[menu.newsletter.button]'|translate}}</button></div>
<div class="text">{{'[menu.newsletter.text]'|translate}}</div>
</div>
</md-sidenav>
<!-- primary content -->
<knk-header class="brand-color mat-elevation-z4" (sidePanelAction)='showSideNav()'></knk-header>
<main ngClass.lt-md="main-xs" ngClass.gt-sm="main-gt-md">
<router-outlet></router-outlet>
</main>
<knk-footer class="brand-color"></knk-footer>
</md-sidenav-container>

View File

@ -0,0 +1,104 @@
@import "../../scss/variables";
@import "../../scss/mixins";
@import "../../scss/palettes";
main {
display: block;
text-align: center;
height: 100%;
&.main-gt-md {
//>= 1280px
width: $main-content-width;
margin: 0 auto;
padding: 0;
}
&.main-xs {
padding: 0 16px;
}
}
.sidenav-container-wraper {
.actions-row {
padding: 15px 20px;
span {
&:first-child {
text-align: left;
}
&:last-child {
text-align: right;
a,
a:active,
a:hover,
a:visited {
text-decoration: none;
border: none;
outline: none;
}
}
}
}
.menu-item {
line-height: 40px;
a,
a:active,
a:hover,
a:visited {
text-decoration: none;
}
&.general-item {
font-size: 17px;
a {
padding-left: 20px;
}
}
&.submenu-item {
font-size: 15px;
a {
padding-left: 35px;
padding-right: 18px;
}
}
}
.newsletter {
margin-top: 120px;
padding-left: 20px;
padding-right: 20px;
.text {
margin-top: 13px;
margin-bottom: 13px;
font-size: 13px;
text-align: center;
}
.mat-input-container {
width: 100%;
border-radius: 6px;
margin: 20px auto;
}
}
a,
a:active,
a:hover,
a:visited {
color: inherit;
}
}
md-sidenav {
width: 300px;
}

View File

@ -0,0 +1,68 @@
import 'hammerjs';
import { TranslateLoaderMock, TokenServiceMock, HeaderServiceMock, UserServiceMock } from '../../mocks';
import { async,
TestBed } from '@angular/core/testing';
import { AppComponent } from './app.component';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { CoreModule } from '../core';
import { FooterComponent } from '../footer';
import { HeaderComponent } from '../core/components';
import { HeaderService, IMenuItem, LoaderService, TokenService, UserService} from '../core/shared';
import { KnkMaterialModule } from '../knk-material';
import { RouterTestingModule } from '@angular/router/testing';
import { Router } from '@angular/router/src/router';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
describe('AppComponent', () => {
//let translateService: TranslateService;
beforeEach(async(() => {
// jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
TestBed.configureTestingModule({
declarations: [
AppComponent,
FooterComponent,
HeaderComponent
],
imports: [
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
}
}),
KnkMaterialModule,
RouterTestingModule,
BrowserAnimationsModule
],
providers: [
{ provide: HeaderService, useValue: new HeaderServiceMock() },
{ provide: LoaderService, useValue: new LoaderService() },
{ provide: TokenService, useValue: new TokenServiceMock() },
{ provide: UserService, useValue: new UserServiceMock() }
],
}).compileComponents();
// translateService = TestBed.get(TranslateService);
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
/*
it(`should have as title 'app'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('app');
}));
*/
/*
it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
}));
*/
});

View File

@ -0,0 +1,93 @@
import { Component, ElementRef, NgZone, Renderer2, ViewChild } from '@angular/core';
import { MdSidenav } from '@angular/material';
import { Subscription } from 'rxjs/Subscription';
import '../../../public/css/styles.scss';
import { HeaderService, IMenuItem, LoaderService, TokenService } from '../core/shared';
import {
Event,
NavigationEnd, NavigationError, NavigationStart, Router
} from '@angular/router';
import 'rxjs/add/operator/filter';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'knk-app',
// styleUrls: ['./app.component.scss'],
templateUrl: './app.component.html'
})
export class AppComponent {
sideMenu: IMenuItem[];
@ViewChild('mobilesidenav') sidenav: MdSidenav;
navigationSubscription: Subscription;
private _nativeElem: HTMLElement;
constructor(router: Router, translateService: TranslateService, private headerService: HeaderService,
private loaderService: LoaderService, private tokenService: TokenService,
private ngZone: NgZone, private renderer: Renderer2, el: ElementRef) {
this._nativeElem = el.nativeElement;
translateService.setDefaultLang('en');
router.events
.filter((e: Event) => e instanceof NavigationError)
.subscribe((e: NavigationError) => {
this.unsubscribeNavigation();
if (e.error === 'miss lang') {
router.navigate([translateService.getDefaultLang(), '404'], { skipLocationChange: true });
}
if (e.error === 'notfound') {
router.navigate([translateService.currentLang, '404'], { skipLocationChange: true });
}
});
router.events
.filter((e: Event) => e instanceof NavigationStart)
.subscribe(() => {
this.ngZone.runOutsideAngular(() => {
this.navigationSubscription = this.loaderService.subscribeLoader(this);
});
});
router.events
.filter((e: Event) => e instanceof NavigationEnd)
.subscribe(() => {
this.ngZone.runOutsideAngular(() => {
this.unsubscribeNavigation();
});
});
this.sideMenu = this.headerService.getMenu();
this.loaderService.loaderComplete.subscribe((isLoaded: boolean) => {
this.toggleLoaded(isLoaded);
});
}
showSideNav(): void {
this.sidenav.open();
}
get isLogin(): boolean {
return this.tokenService.isLogin;
}
public logout(): void {
this.tokenService.releaseToken();
}
private unsubscribeNavigation(): void {
if (this.navigationSubscription) {
this.navigationSubscription.unsubscribe();
}
}
private toggleLoaded(loaded: boolean): void {
let loadingClass: string = 'loading';
this.ngZone.runOutsideAngular(() => {
if (loaded) {
this.renderer.removeClass(this._nativeElem, loadingClass);
} else {
this.renderer.addClass(this._nativeElem, loadingClass);
}
});
}
}

View File

@ -0,0 +1,49 @@
import 'hammerjs';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FlexLayoutModule } from '@angular/flex-layout';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { HttpModule } from '@angular/http';
// import { LdiMaterialModule } from '../knk-material';
import { routing } from './app.routing';
import { CoreModule } from '../core';
import { FooterModule } from '../footer';
import { AppComponent } from './app.component';
// AoT requires an exported function for factories
export function HttpLoaderFactory(http: HttpClient): TranslateLoader {
return new TranslateHttpLoader(http, 'i18n/');
}
@NgModule({
bootstrap: [AppComponent],
declarations: [AppComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
routing,
HttpModule,
FooterModule,
HttpClientModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useFactory: HttpLoaderFactory,
deps: [HttpClient]
}
}),
// LdiMaterialModule.forRoot(),
CoreModule.forRoot(),
FlexLayoutModule
]
})
export class AppModule { }

View File

@ -0,0 +1,15 @@
import { ModuleWithProviders } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
export const routes: Routes = [
// {
// path: 'heroes', loadChildren: '../lazy/lazy.module#LazyModule'
// },
{
path: '',
redirectTo: 'en/registration',
pathMatch: 'full'
}
];
export const routing: ModuleWithProviders = RouterModule.forRoot(routes, { enableTracing: true });

View File

@ -0,0 +1,30 @@
<div class="header" fxLayout="row" fxLayoutAlign="space-between center" fxHide fxShow.gt-sm>
<span class="logo"><!--img src="" height="52" alt=""--></span>
<div fxFlex="550px" fxLayout="row" fxLayoutAlign="space-around" fxFlexAlign="center start">
<span *ngFor="let item of menuItems; first as isFirst">
<a *ngIf="!(item.rout.startsWith('https')||item.rout.startsWith('http'))" routerLink="{{item.rout}}" [ngClass]="{'active' : item.isActive}" (click)="setActiveMenu(item)">{{item.translateKey|translate}}</a>
<a *ngIf="(item.rout.startsWith('https')||item.rout.startsWith('http'))" href="{{item.rout}}" target="_blank" [ngClass]="{'active' : item.isActive}" (click)="setActiveMenu(item)">{{item.translateKey|translate}}</a>
</span>
</div>
<span class="action" fxFlexAlign="center" *ngIf="!isLogin"><a md-line routerLink="en/login">{{'[menu.signin]'|translate}}</a></span>
<div fxLayout="row" *ngIf="isLogin" fxLayoutAlign="end center">
<span [style.cursor]='"pointer"' [mdMenuTriggerFor]="user">{{'[menu.my account]'|translate}}</span>
<span class="separator">|</span>
<a md-line routerLink="en/login" (click)="logout()">{{'[menu.signout]'|translate}}</a>
</div>
</div>
<div class="header-xs" fxLayout="row" fxLayoutAlign="space-between center" fxHide fxShow.lt-md>
<i class="material-icons" (click)="openSideNav()">menu</i>
<!--img src="" alt="logo" height="22"-->
<i class="material-icons">person_outline</i>
</div>
<md-menu #user="mdMenu" xPosition="before">
<div class="userinfo">
{{ accounts?.user.firstName+' '+accounts?.user.lastName }}
</div>
<hr>
<button md-menu-item [routerLink]="['/',lang,'myaccount']">
My Profile
</button>
</md-menu>

View File

@ -0,0 +1,60 @@
@import '~@angular/material/theming';
@import "../../../../scss/variables";
@import "../../../../scss/mixins";
@import "../../../../scss/palettes";
:host {
width: 100%;
display: block;
}
.header {
max-width: $main-content-width;
margin: auto;
padding: 16px 0;
font-size: 15px;
.separator {
margin-left: 10px;
margin-right: 10px;
}
&.header-gt-sm {
//>= 960
padding-left: 0;
padding-right: 0;
}
.logo {
text-align: left;
font-size: 24px;
font-weight: bold;
cursor: pointer;
line-height: 15px;
}
.action {
text-align: right;
a {
padding: 7px 20px;
border-radius: 5px;
white-space: nowrap;
}
}
}
/* header for small resolution */
.header-xs {
padding: 10px 16px;
i{
cursor: pointer;
}
}
.userinfo {
font-size: 16px;
padding: 0 16px;
text-align: center;
}

View File

@ -0,0 +1,65 @@
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { Subscription } from 'rxjs/Subscription';
import { HeaderService, IMenuItem, IUserAccounts, TokenService, UserService } from '../../shared';
@Component({
selector: 'knk-header',
styleUrls: ['header.component.scss'],
templateUrl: 'header.component.html'
})
export class HeaderComponent implements OnDestroy, OnInit {
@Output() sidePanelAction: EventEmitter<boolean> = new EventEmitter<boolean>();
menuItems: IMenuItem[];
accounts: IUserAccounts;
authorizationChangedSubscription: Subscription;
constructor(private headerService: HeaderService,
private tokenService: TokenService,
private userService: UserService,
private translate: TranslateService) { }
ngOnInit(): void {
this.menuItems = this.headerService.getMenu();
this.authorizationChangedSubscription = this.tokenService.authorizationChanged.subscribe(isLogin => {
if (isLogin) {
this.userService.loadUser().subscribe(accounts => {
this.accounts = accounts;
});
} else {
this.accounts = null;
}
});
if (this.tokenService.isLogin) {
this.userService.loadUser().subscribe(accounts => {
this.accounts = accounts;
});
}
}
ngOnDestroy(): void {
if (this.authorizationChangedSubscription) { this.authorizationChangedSubscription.unsubscribe(); }
}
setActiveMenu(item: IMenuItem): void {
this.menuItems.forEach((value: IMenuItem) => value.isActive = false);
item.isActive = true;
}
openSideNav(): void {
this.sidePanelAction.emit();
}
get isLogin(): boolean {
return this.tokenService.isLogin;
}
get lang(): string {
return this.translate.currentLang;
}
public logout(): void {
console.log('clear');
this.tokenService.releaseToken();
}
}

View File

@ -0,0 +1 @@
export { HeaderComponent } from './header.component';

View File

@ -0,0 +1,3 @@
export * from './notfound';
export * from './registration';
export * from './header';

View File

@ -0,0 +1 @@
export { NotFoundComponent } from './notfound.component';

View File

@ -0,0 +1 @@
<h1>{{'[page not found]' | translate}}</h1>

View File

@ -0,0 +1,58 @@
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NotFoundComponent } from './notfound.component';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { BaseRequestOptions, HttpModule, Http, Response, ResponseOptions, XHRBackend } from '@angular/http';
import { HttpClient, HttpClientModule} from '@angular/common/http';
//import { MockBackend, MockConnection } from '@angular/http/testing';
import { ConnectionBackend, RequestOptions } from '@angular/http';
import {TranslateLoaderMock} from '../../../../mocks'
describe('notfound component test', () => {
let notfoundComponent: ComponentFixture<NotFoundComponent>;
let translateService: TranslateService;
//let mockBackend: MockBackend;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [NotFoundComponent],
imports: [HttpModule, HttpClientModule, TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
//useFactory: (http: HttpClient) => new TranslateHttpLoader(http),
//deps: [HttpClient]
}
})],
providers: [
//MockBackend, BaseRequestOptions,
// {
// provide: Http,
// deps: [MockBackend, BaseRequestOptions],
// useFactory: (backend: XHRBackend, defaultOptions: BaseRequestOptions) => {
// return new Http(backend, defaultOptions);
// }
//}
]
}).compileComponents().then(() => {
notfoundComponent = TestBed.createComponent(NotFoundComponent);
translateService = TestBed.get(TranslateService);
//mockBackend = TestBed.get(ConnectionBackend);
});
}));
it('translate', fakeAsync(() => {
//this.mockBackend.connections.subscribe((connection: MockConnection) => {
// connection.mockRespond(new Response(new ResponseOptions({
// body: { '[page not found]': 'page not found2'}
// })));
translateService.use('en');
tick();
notfoundComponent.detectChanges();
expect(notfoundComponent.debugElement.query(By.css('h1')).nativeElement.innerHTML).toBe('page not found');
}));
});

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'knk-notfound',
styleUrls: ['./notfound.component.scss'],
templateUrl: './notfound.component.html'
})
export class NotFoundComponent {
}

View File

@ -0,0 +1 @@
export { RegistrationComponent } from './registration.component';

View File

@ -0,0 +1,64 @@
<md-card>
<md-card-title>Registration</md-card-title>
<md-card-content>
<md-list>
<md-list-item>
<md-input-container class="full-width-input">
<input mdInput placeholder="{{'[user name]' | translate}}" [(ngModel)]="user.userName" [type]="'text'" required/>
</md-input-container>
</md-list-item>
<md-list-item>
<md-input-container class="full-width-input">
<input mdInput placeholder="Password" [(ngModel)]="user.password" [type]="'password'" required/>
</md-input-container>
</md-list-item>
<md-list-item>
<md-input-container class="full-width-input">
<input mdInput placeholder="First name" [(ngModel)]="user.firstName" [type]="'text'" required/>
</md-input-container>
</md-list-item>
<md-list-item>
<md-input-container class="full-width-input">
<input mdInput placeholder="Last name" [(ngModel)]="user.lastName" [type]="'text'" required/>
</md-input-container>
</md-list-item>
<md-list-item>
<md-input-container class="full-width-input">
<input mdInput placeholder="Email" [type]="'mail'" [(ngModel)]="user.email" required/>
</md-input-container>
</md-list-item>
<md-list-item>
<md-input-container class="full-width-input">
<span md-prefix>+</span>
<input mdInput placeholder="Phone" [type]="tel" [(ngModel)]="user.phone" required/>
</md-input-container>
</md-list-item>
<md-list-item>
<div class="full-width-input">
<md-select placeholder="The interface language" [(ngModel)]="user.languageCode">
<md-option *ngFor="let option of languages" [value]="option.id" class="mat-primary">{{option.value}}</md-option>
</md-select>
</div>
</md-list-item>
<md-list-item style="height:40px;">
<md-radio-group [(ngModel)]="user.gender">
<md-radio-button *ngFor="let gender of [{val:'F',label:'Female'},{val:'M', label:'Male'}]" [value]="gender.val" aria-label="{{ gender.label }}">
{{ gender.label }}
</md-radio-button>
</md-radio-group>
</md-list-item>
</md-list>
<button md-button (click)="register($event)">Register</button>
</md-card-content>
</md-card>
<md-card *ngIf="registrationError" class="registration-error mat-elevation-z0">
<md-card-content>
<pre>{{registrationError.msg}}</pre>
</md-card-content>
</md-card>
<div>
<h1>{{user.firstName | lowercase }}</h1>
<h1>{{user.firstName | slice:3 }}</h1>
</div>

View File

@ -0,0 +1,6 @@
.phone-prefix-input{
width: 150px;
}
.registration-error{
text-align: left;
}

View File

@ -0,0 +1,75 @@
import { Component, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { UniqueSelectionDispatcher } from '@angular/material';
import { ActivatedRoute, Router } from '@angular/router';
import { TokenService } from '../../shared';
import { ICountry } from '../../shared/country/country.model';
import { CountryService } from '../../shared/country/country.service';
import { IUser, User } from '../../shared/user/user.model';
import { UserService } from '../../shared/user/user.service';
@Component({
providers: [UniqueSelectionDispatcher],
selector: 'knk-registration',
styleUrls: ['./registration.component.scss'],
templateUrl: './registration.component.html'
})
export class RegistrationComponent implements OnInit {
user: IUser;
registrationError: { id: number, msg: string } = null;
languages: { id: string, value: string }[] = [
{ value: 'English', id: 'en' },
{ value: 'Español', id: 'es' },
{ value: '中文', id: 'zh' },
{ value: 'Português', id: 'pt' },
{ value: '日本語', id: 'ja' },
{ value: 'Русский', id: 'ru' }
];
constructor(
private userService: UserService,
private tokenService: TokenService,
private router: Router,
private location: Location,
private countryService: CountryService,
private route: ActivatedRoute) {
this.user = new User();
}
ngOnInit(): void {
let langParamName: string = 'lang';
this.location.replaceState(`/${this.route.snapshot.params[langParamName]}/registration`); // clear query string parameters
this.countryService.Country.subscribe((country: ICountry) => {
if (country) {
this.user.countryCode2 = country.countryCode2;
this.user.languageCode = this.route.snapshot.params[langParamName];
// console.log('Country code is ' + country.countryCode2);
} else {
console.log('Country code is not found');
}
});
let isTestPropertyName: string = 'test';
this.userService.testParam = this.route.snapshot.data[isTestPropertyName];
}
register(e: Event): void {
e.preventDefault();
this.registrationError = null;
this.userService.createUser(this.user).subscribe(
(u) => {
console.log('registerd, login and go to dashboard');
this.tokenService.apiUserToken(u).subscribe(() => {
let langParamName: string = 'lang';
this.router.navigate([this.route.snapshot.params[langParamName], `dashboard`]);
});
},
(error) => {
console.log(error);
this.registrationError = error;
});
}
}

View File

@ -0,0 +1,37 @@
import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { FlexLayoutModule } from '@angular/flex-layout';
import { TranslateModule } from '@ngx-translate/core';
import { KnkMaterialModule } from '../knk-material';
import { CoreRoutingModule } from './core.routing';
import {
HeaderComponent, NotFoundComponent, RegistrationComponent
} from './components';
import {
ApiConsumerTokenResolve, ApiUserTokenResolve, CountryService, HeaderService, LanguageResolve,
LoaderService, TestParameterResolve, TokenService, UserService, UserTokenResolve
} from './shared';
@NgModule({
imports: [CoreRoutingModule, CommonModule, FormsModule, KnkMaterialModule, TranslateModule, FlexLayoutModule],
exports: [KnkMaterialModule, NotFoundComponent, RegistrationComponent, HeaderComponent],
declarations: [NotFoundComponent, RegistrationComponent, HeaderComponent],
providers: [UserService, HeaderService]
})
export class CoreModule {
static forRoot(): ModuleWithProviders {
console.log('core module for root');
return {
ngModule: CoreModule,
providers: [
UserService, ApiUserTokenResolve, ApiConsumerTokenResolve, CountryService, LanguageResolve, UserTokenResolve,
TokenService, TestParameterResolve, HeaderService, LoaderService]
};
}
constructor() { console.log('core module ctor'); }
}

View File

@ -0,0 +1,45 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { NotFoundComponent, RegistrationComponent } from './components';
import { ApiConsumerTokenResolve, LanguageResolve, TestParameterResolve } from './shared';
export const coreRoutes: Routes = [
{
path: ':lang',
resolve: {
lang: LanguageResolve
},
children: [
{
path: 'registration',
component: RegistrationComponent,
resolve: {
token: ApiConsumerTokenResolve,
test: TestParameterResolve
}
},
{
path: 'login', loadChildren: '../login/login.module#LoginModule'
},
{
path: '404',
component: NotFoundComponent
},
{
path: 'myaccount', loadChildren: '../myaccount/myaccount.module#MyAccountModule'
},
{
path: 'dashboard',
redirectTo: '',
pathMatch: 'full'
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(coreRoutes)],
exports: [RouterModule]
})
export class CoreRoutingModule { }

View File

@ -0,0 +1 @@
export { CoreModule } from './core.module';

View File

@ -0,0 +1,15 @@
export interface ICountry {
countryId: number;
countryName: string;
countryDesc: string;
regionId: number;
countryCode2: string;
countryCode3: string;
numCode: string;
phonePrefix: string;
currency: string;
}
export interface ICountries {
items: ICountry[];
}

View File

@ -0,0 +1,58 @@
import { Http, RequestOptions, Response } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { ICountries, ICountry } from './country.model';
import { TokenService } from '../token/token.service';
import { IApiError, IApiResponseError, IApiResponseSuccess, isApiResponseError } from '../token/apiresponse.model';
@Injectable()
export class CountryService {
country: ICountry;
constructor(private http: Http, private tokenService: TokenService) { }
get Country(): Observable<ICountry> {
if (this.country) {
return Observable.of(this.country);
}
return this.http
.get('//geoip.nekudo.com/api/')
.map((response: Response) => {
const {country: {code}}: { country: { code: string } } = response.json();
return code;
})
.flatMap((countryCode2: string) =>
this.http
.get(`${SETTINGS.API_URL}/general/Countries/${countryCode2}`,
new RequestOptions({ headers: this.tokenService.authorizationHeaders })))
.map((response: Response) => {
const data: IApiResponseError | IApiResponseSuccess<ICountries> = response.json();
if (isApiResponseError(data)) {
return Observable.throw(data.error);
}
return data.result;
})
.map((countries: ICountries) => {
// const [country]: ICountry[] = countries.items;
if (countries.items.length > 0) {
this.country = countries.items[0];
return countries.items[0];
}
}).catch(this.handleError);
}
private handleError(error: IApiError): Observable<ICountry> {
return Observable.throw(error.msg || error);
}
}

View File

@ -0,0 +1,2 @@
export * from './country.service';
export * from './country.model';

View File

@ -0,0 +1,9 @@
export interface IMenuItem {
name: string;
translateKey: string;
rout: string;
isActive: boolean;
isVisible: boolean;
subMenu?: IMenuItem[];
absolute?: string;
}

View File

@ -0,0 +1,15 @@
import { Injectable } from '@angular/core';
import { IMenuItem } from '../../shared/header/header.model';
@Injectable()
export class HeaderService {
getMenu(): IMenuItem[] {
const menu: IMenuItem[] = [];
// menu.push({ name: 'how it', translateKey: '[menu.how it]', rout: '', isActive: false, isVisible: true });
// menu.push({ name: 'about us', translateKey: '[menu.about us]', rout: '', isActive: false, isVisible: true });
return menu;
}
}

View File

@ -0,0 +1,2 @@
export * from './header.model';
export * from './header.service';

View File

@ -0,0 +1,8 @@
export * from './country';
export * from './language';
export * from './testparam';
export * from './token';
export * from './user';
export * from './header';
export * from './storage';
export * from './loader';

View File

@ -0,0 +1 @@
export * from './language.resolve';

View File

@ -0,0 +1,42 @@
import { async, TestBed } from '@angular/core/testing';
import { LanguageResolve } from './language.resolve';
import { ActivatedRouteSnapshotMock, TranslateLoaderMock } from '../../../../mocks';
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
describe('Language resolve checking', () => {
let activatedRouteSnapshotStub: ActivatedRouteSnapshotMock;
let translateService: TranslateService;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: TranslateLoaderMock
}
})]
});
activatedRouteSnapshotStub = new ActivatedRouteSnapshotMock();
translateService = TestBed.get(TranslateService);
}));
it('check language done with en language', async(() => {
activatedRouteSnapshotStub.params = { lang: 'en' };
let translations: { [name: string]: string } = { '[page not found]': 'page not found' };
let resolver: LanguageResolve = new LanguageResolve(translateService);
resolver.resolve(activatedRouteSnapshotStub).subscribe(
r => expect(r).toEqual(translations),
e => console.log('err', e)
);
}));
it('check language throw exception with ru language', async(() => {
activatedRouteSnapshotStub.params = { lang: 'ru' };
let resolver: LanguageResolve = new LanguageResolve(translateService);
expect(resolver.resolve).toThrow();
}));
});

View File

@ -0,0 +1,26 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { TranslateService } from '@ngx-translate/core';
@Injectable()
export class LanguageResolve implements Resolve<{ [name: string]: string }> {
constructor(private translateService: TranslateService) { }
resolve(route: ActivatedRouteSnapshot): Observable<{ [name: string]: string }> {
let languageParamName: string = 'lang';
return this.translateService.use(route.params[languageParamName])
.catch(er => {
console.log('err in resolve', er);
return Observable.throw('missed lang');
});
}
}

View File

@ -0,0 +1 @@
export { LoaderService } from './loader.service';

View File

@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
@Injectable()
export class LoaderService {
public loaderComplete: Observable<boolean>;
private loaders: Set<Object> = new Set<Object>();
private loaderCompleteSource: Subject<boolean> = new Subject<boolean>();
constructor() {
this.loaderComplete = this.loaderCompleteSource.asObservable();
}
public subscribeLoader(owner: Object): Subscription {
if (this.loaders.size === 0) {
this.loaderCompleteSource.next(false);
}
this.loaders.add(owner);
console.log('LoaderService', this.loaders.size, 'subscribe', owner);
return new Subscription(() => {
console.log('LoaderService', this.loaders.size, 'unsubscribe', owner);
const size: number = this.loaders.size;
this.loaders.delete(owner);
if (size !== this.loaders.size && this.loaders.size === 0) {
console.log('LoaderService', this.loaders.size, 'load complete', owner);
this.loaderCompleteSource.next(true);
}
});
}
}

View File

@ -0,0 +1 @@
export * from './storage';

View File

@ -0,0 +1,72 @@
import { Cookie } from 'ng2-cookies/ng2-cookies';
import { IToken } from '../token';
let LOANREQUESTID_COOKIE_NAME: string = 'loanRequestId';
let LOANREQUESTUID_COOKIE_NAME: string = 'loanRequestUid';
let LEADUID_COOKIE_NAME: string = 'leaduid';
let TOKEN_COOKIE_NAME: string = 'Knk-token';
export class LdiStorage {
private static cookiesNames = [LOANREQUESTID_COOKIE_NAME, LOANREQUESTUID_COOKIE_NAME, LEADUID_COOKIE_NAME, TOKEN_COOKIE_NAME];
public static Clear(): void {
LdiStorage.cookiesNames.forEach(cookie => {
LdiStorage.DeleteFromCookies(cookie);
});
}
public static SaveToken(token: IToken): void {
// save expiry /2 from server expiry
LdiStorage.SaveToCookies(TOKEN_COOKIE_NAME, token, token.expiry / 2 / 60 / 60 / 24);
}
public static SaveLoanRequestId(loanRequestId: number): void {
LdiStorage.SaveToCookies(LOANREQUESTID_COOKIE_NAME, loanRequestId);
}
public static SaveLoanRequestUid(loanRequestUid: string): void {
LdiStorage.SaveToCookies(LOANREQUESTUID_COOKIE_NAME, loanRequestUid);
}
public static SaveLeadUid(leadUid: string): void {
LdiStorage.SaveToCookies(LEADUID_COOKIE_NAME, leadUid);
}
public static DeleteToken(): void {
LdiStorage.DeleteFromCookies(TOKEN_COOKIE_NAME);
}
public static DeleteLoanRequestId(): void {
LdiStorage.DeleteFromCookies(LOANREQUESTID_COOKIE_NAME);
}
public static DeleteLoanRequestUid(): void {
LdiStorage.DeleteFromCookies(LOANREQUESTUID_COOKIE_NAME);
}
public static DeleteLeadUid(): void {
LdiStorage.DeleteFromCookies(LEADUID_COOKIE_NAME);
}
public static get Token(): IToken {
return <IToken>LdiStorage.LoadFromCookies(TOKEN_COOKIE_NAME);
}
public static get LoanRequestId(): number {
return <number>LdiStorage.LoadFromCookies(LOANREQUESTID_COOKIE_NAME);
}
public static get LoanRequestUid(): string {
return <string>LdiStorage.LoadFromCookies(LOANREQUESTUID_COOKIE_NAME);
}
public static get LeadUid(): string {
return <string>LdiStorage.LoadFromCookies(LEADUID_COOKIE_NAME);
}
private static SaveToCookies(cookieName: string, value: Object, expires: number = 1, path: string = '/'): void {
Cookie.set(cookieName, JSON.stringify(value), expires, path);
}
private static DeleteFromCookies(cookieName: string, path: string = '/'): void {
Cookie.delete(cookieName, path);
}
private static LoadFromCookies(cookiesName: string): {} {
const value: string = Cookie.get(cookiesName);
if (value) {
return JSON.parse(value);
}
return null;
}
}

View File

@ -0,0 +1 @@
export * from './testparam.resolve';

View File

@ -0,0 +1,55 @@
import { async } from '@angular/core/testing';
import { TestParameterResolve } from './testparam.resolve';
import { ActivatedRouteSnapshotMock } from '../../../../mocks';
describe('Testparam resolve checking', () => {
let activatedRouteSnapshotStub: ActivatedRouteSnapshotMock;
beforeEach(() => {
activatedRouteSnapshotStub = new ActivatedRouteSnapshotMock();
});
it('chech testparam done with test value', async(() => {
activatedRouteSnapshotStub.params = { test: 'test' };
let resolver: TestParameterResolve = new TestParameterResolve();
resolver.resolve(activatedRouteSnapshotStub).then(
(r: string) => expect(r).toEqual(activatedRouteSnapshotStub.params.test),
(e: {}) => console.log('err', e)
);
}));
it('chech testparam done with empty string', async(() => {
activatedRouteSnapshotStub.params = { test: '' };
let resolver: TestParameterResolve = new TestParameterResolve();
resolver.resolve(activatedRouteSnapshotStub).then(
(r: string) => expect(r).toEqual(activatedRouteSnapshotStub.params.test),
(e: {}) => fail(e)
);
}));
it('chech testparam done with empty params', async(() => {
activatedRouteSnapshotStub.params = {};
let resolver: TestParameterResolve = new TestParameterResolve();
resolver.resolve(activatedRouteSnapshotStub).then(
(r: string) => expect(r).toEqual(''),
(e: {}) => fail(e)
);
}));
it('chech testparam fail with string not empty or test', async(() => {
activatedRouteSnapshotStub.params = { test: 'tst2' };
let resolver: TestParameterResolve = new TestParameterResolve();
resolver.resolve(activatedRouteSnapshotStub).then(
() => fail('promise not rejected'),
(e: string) => expect(e).toEqual('notfound')
);
}));
it('chech testparam done with params not have test param', async(() => {
activatedRouteSnapshotStub.params = { custom: 'tst2' };
let resolver: TestParameterResolve = new TestParameterResolve();
resolver.resolve(activatedRouteSnapshotStub).then(
(r: string) => expect(r).toEqual(''),
(e: object) => fail(e)
);
}));
});

View File

@ -0,0 +1,17 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve } from '@angular/router';
@Injectable()
export class TestParameterResolve implements Resolve<string> {
resolve(route: ActivatedRouteSnapshot): Promise<string> {
return new Promise((resolve, reject) => {
let testParamPropertyName: string = 'test';
const test: string = route.params[testParamPropertyName];
if (test == null || test === '' || test === 'test') {
resolve(test || '');
} else {
reject('notfound');
}
});
}
}

View File

@ -0,0 +1,13 @@
export interface IApiResponseSuccess<T> {
result: T;
}
export interface IApiResponseError {
error: IApiError;
}
export interface IApiError {
id: number;
msg: string;
}
export function isApiResponseError<T>(data: IApiResponseError | IApiResponseSuccess<T>): data is IApiResponseError {
return (<IApiResponseError>data).error != null;
}

View File

@ -0,0 +1,4 @@
export * from './token.service';
export * from './token.model';
export * from './token.resolve';
export * from './apiresponse.model';

View File

@ -0,0 +1,6 @@
export interface IToken {
token: string;
expiry: number;
isUser: boolean;
isOperator: boolean;
}

View File

@ -0,0 +1,46 @@
import { Injectable } from '@angular/core';
import { ActivatedRoute, Resolve, Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { IToken } from './token.model';
import { TokenService } from './token.service';
@Injectable()
export class ApiConsumerTokenResolve implements Resolve<IToken> {
constructor(private tokenService: TokenService) { }
resolve(): Observable<IToken> {
return this.tokenService.apiConsumerToken();
}
}
@Injectable()
export class UserTokenResolve implements Resolve<boolean> {
constructor(private tokenService: TokenService, private router: Router, private route: ActivatedRoute) { }
resolve(): boolean {
if (this.tokenService.isLogin) {
console.log('resolve', this.route);
this.router.navigate(['en', 'dashboard']);
return false;
}
return true;
}
}
@Injectable()
export class ApiUserTokenResolve implements Resolve<IToken | boolean> {
constructor(private tokenService: TokenService, private router: Router) { }
resolve(): IToken | boolean {
if (!this.tokenService.isLogin) {
this.router.navigate(['en', 'login']);
return false;
}
return this.tokenService.authorizationToken;
}
}

View File

@ -0,0 +1,112 @@
import { TestBed } from '@angular/core/testing';
import { BaseRequestOptions, Http, HttpModule, Response, ResponseOptions, XHRBackend } from '@angular/http';
import { MockBackend, MockConnection } from '@angular/http/testing';
import { IToken } from './token.model';
import { TokenService } from './token.service';
import { LdiStorage } from '../storage';
describe('Token server tests', () => {
let tokenService: TokenService;
let mockBackend: MockBackend;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpModule],
providers: [TokenService,
MockBackend, BaseRequestOptions,
{
provide: Http,
deps: [MockBackend, BaseRequestOptions],
useFactory: (backend: XHRBackend, defaultOptions: BaseRequestOptions) => {
return new Http(backend, defaultOptions);
}
}]
});
tokenService = TestBed.get(TokenService);
// tokenService.authorizationToken = null;
LdiStorage.Clear();
mockBackend = TestBed.get(MockBackend);
});
it('Get correct api consumer token service', () => {
mockBackend.connections.subscribe((connection: MockConnection) => {
connection.mockRespond(new Response(new ResponseOptions({
body: {
result: {
token: 'correct token',
expiry: new Date()
}
}
})));
});
expect(tokenService).not.toBeNull('this not null');
tokenService.apiConsumerToken().subscribe((token: IToken) => {
expect(token.token).toEqual('correct token');
});
});
it('Get fail api consumer token service', () => {
let error: { id: number, msg: string } = {
id: 1,
msg: 'error'
};
mockBackend.connections.subscribe((connection: MockConnection) => {
connection.mockRespond(new Response(new ResponseOptions({ body: { error } })));
});
expect(tokenService).not.toBeNull('this not null');
tokenService.apiConsumerToken().subscribe(
(token: IToken) => expect(token.token).toBe(undefined, 'should not be resolved'),
err => expect(err).toEqual(jasmine.objectContaining(error)));
});
const testUser: { userName: string, password: string } = { userName: 'user', password: 'pass' };
const apiUserTokenConnectionSubscribe: (connection: MockConnection) => void = (connection: MockConnection) => {
let user: { userName: string, password: string } = connection.request.json();
if (user.userName === 'user' && user.password === 'pass') {
connection.mockRespond(new Response(new ResponseOptions({
body: {
result: {
token: 'correct token',
expiry: new Date()
}
}
})));
} else {
connection.mockRespond(new Response(new ResponseOptions({
body: {
error: {
id: 3,
msg: 'invalid username or password'
}
}
})));
}
};
it('Get correct api user token service', () => {
mockBackend.connections.subscribe(apiUserTokenConnectionSubscribe);
expect(tokenService).not.toBeNull('this not null');
tokenService.apiUserToken(testUser).subscribe((token: IToken) => {
expect(token.token).toEqual('correct token');
});
});
it('Get fail api consumer token service', () => {
let error: { id: number, msg: string } = {
id: 3,
msg: 'invalid username or password'
};
mockBackend.connections.subscribe(apiUserTokenConnectionSubscribe);
tokenService.apiUserToken({ userName: 'user', password: 'p' }).subscribe(
(token: IToken) => expect(token.token).toBe(undefined, 'should not be resolved'),
err => expect(err).toEqual(jasmine.objectContaining(error)));
});
});

View File

@ -0,0 +1,95 @@
import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { IApiError, IApiResponseError, IApiResponseSuccess, isApiResponseError } from './apiresponse.model';
import { IToken } from './token.model';
import { IUserCredentials } from '../user/user.model';
import { LdiStorage } from '../storage';
let TOKEN_API_URL: string = `${SETTINGS.API_URL}/token/${SETTINGS.API_IDENTIFIER}`;
@Injectable()
export class TokenService {
public authorizationChanged: Observable<boolean>;
private authorizationChangeSource = new Subject<boolean>();
constructor(private http: Http) {
console.log('token service ctor');
this.authorizationChanged = this.authorizationChangeSource.asObservable();
}
get authorizationToken(): IToken {
return LdiStorage.Token;
}
get authorization(): string {
return this.authorizationToken == null ? '' : `Bearer ${this.authorizationToken.token}`;
}
get authorizationHeaders(): Headers {
return new Headers({ Authorization: this.authorization });
}
apiConsumerToken(): Observable<IToken> {
if (this.authorizationToken) {
return Observable.of(this.authorizationToken);
}
const headers: Headers = new Headers({ 'Content-Type': 'application/json' });
const requestOptions: RequestOptions = new RequestOptions({ headers });
return this.http.get(TOKEN_API_URL, requestOptions)
.map((response: Response) => {
const data: IApiResponseError | IApiResponseSuccess<IToken> = response.json();
if (isApiResponseError(data)) {
throw data.error;
// return Observable.throw(data.error);
}
// this.authorizationToken = data.result;
// LdiStorage.SaveToken(this.authorizationToken);
LdiStorage.SaveToken(data.result);
return data.result;
}).catch(this.handleError);
}
apiUserToken(userCredentials: IUserCredentials): Observable<IToken> {
if (this.authorizationToken && this.authorizationToken.isUser) {
return Observable.of(this.authorizationToken);
}
const headers: Headers = new Headers({ 'Content-Type': 'application/json' });
const requestOptions: RequestOptions = new RequestOptions({ headers });
return this.http.post(TOKEN_API_URL,
userCredentials || { userName: null, password: null }, requestOptions)
.map((response: Response) => {
const data: IApiResponseError | IApiResponseSuccess<IToken> = response.json();
if (isApiResponseError(data)) {
throw data.error;
}
// this.authorizationToken = data.result;
// this.authorizationToken.isLogin = true;
// LdiStorage.SaveToken(this.authorizationToken);
LdiStorage.SaveToken(data.result);
this.authorizationChangeSource.next(true);
return data.result;
}).catch(this.handleError);
}
get isLogin(): boolean {
return this.authorizationToken && this.authorizationToken.isUser;
}
public releaseToken(): void {
LdiStorage.Clear();
this.authorizationChangeSource.next(false);
}
private handleError(error: IApiError): Observable<IToken> {
return Observable.throw(error);
}
}

View File

@ -0,0 +1,2 @@
export * from './user.model';
export * from './user.service';

View File

@ -0,0 +1,84 @@
export interface IUserCredentials {
userName: string;
password: string;
}
export interface IUser extends IUserCredentials {
userId?: number;
leadId?: number;
gender: string;
countryCode2: string;
languageCode: string;
isTest?: boolean;
// Personal Details
firstName: string;
lastName: string;
dateOfBirthday: Date;
address: string;
city: string;
state: string;
postCode: string;
email: string;
phone: string;
likeToGetNewslatter: boolean;
isAnonymous: boolean;
}
export class UserCredentials implements IUserCredentials {
userName: string;
password: string;
}
export class User implements IUser {
userName: string;
leadId?: number;
userId?: number;
password: string;
gender: string;
countryCode2: string;
languageCode: string;
// Personal Details
firstName: string;
lastName: string;
dateOfBirthday: Date;
address: string;
city: string;
state: string;
postCode: string;
email: string;
phone: string;
likeToGetNewslatter: boolean;
isAnonymous: boolean;
constructor() {
/*
this.firstName = lead.firstName;
this.lastName = lead.lastName;
this.dateOfBirthday = lead.dateOfBirthday;
this.address = lead.address;
this.city = lead.city;
this.state = lead.state;
this.postCode = lead.postCode;
this.email = lead.email;
this.phone = lead.phone;
this.likeToGetNewslatter = lead.likeToGetNewslatter;
this.isAnonymous = lead.isAnonymous;
this.leadId = lead.leadId;
*/
}
}
export interface IAccount {
accountId: number;
accountCurrency: string;
userId: number;
brandId: number;
accountTypeId: number;
allowMarketPrimary: boolean;
allowMarketSecondary: boolean;
isDemo: boolean;
accountStatusId: number;
currentBalance: number;
}
export interface IUserAccounts {
user: IUser;
accounts: IAccount[];
}

View File

@ -0,0 +1,84 @@
import { Injectable } from '@angular/core';
import { Headers, Http, RequestOptions, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/throw';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import { IUser, IUserAccounts, IUserCredentials } from './user.model';
import { TokenService } from '../token/token.service';
import { IApiError, IApiResponseError, IApiResponseSuccess, isApiResponseError } from '../token/apiresponse.model';
@Injectable()
export class UserService {
user: IUser;
userAccounts: IUserAccounts;
testParam: string;
get UserCredentials(): IUserCredentials {
return this.user || this.userCredentials;
}
set UserCredentials(value: IUserCredentials) {
this.userCredentials = value;
}
private userCredentials: IUserCredentials;
constructor(private http: Http, private tokenService: TokenService) { }
createUser(user: IUser): Observable<IUser> {
const headers: Headers = new Headers({
'Content-Type': 'application/json',
Authorization: this.tokenService.authorization
});
const options: RequestOptions = new RequestOptions({ headers });
const req: Observable<Response> = this.http.post(`${SETTINGS.API_URL}/general/newuser/${this.testParam}`, user, options);
return req.map((response: Response) => {
const data: IApiResponseError | IApiResponseSuccess<number> = response.json();
if (isApiResponseError(data)) {
throw data.error;
// return Observable.throw(data.error.msg);
}
user.userId = data.result;
this.user = user;
return user;
}).catch(this.handleError);
}
loadUser(): Observable<IUserAccounts> {
const headers: Headers = new Headers({
'Content-Type': 'application/json',
Authorization: this.tokenService.authorization
});
const options: RequestOptions = new RequestOptions({ headers });
const req: Observable<Response> = this.http.get(`${SETTINGS.API_URL}/account/useraccounts`, options);
return req.map((response: Response) => {
const data: IApiResponseError | IApiResponseSuccess<IUserAccounts> = response.json();
if (isApiResponseError(data)) {
throw data.error;
// return Observable.throw(data.error.msg);
}
this.userAccounts = data.result;
return data.result;
});
}
updateUser(user: IUser): Observable<IUser> {
const headers: Headers = new Headers({
'Content-Type': 'application/json',
Authorization: this.tokenService.authorization
});
const options: RequestOptions = new RequestOptions({ headers });
const req: Observable<Response> = this.http.put(`${SETTINGS.API_URL}/general/user`, user, options);
return req.map((response: Response) => {
const data: IApiResponseError | IApiResponseSuccess<IUser> = response.json();
if (isApiResponseError(data)) {
throw data.error;
// return Observable.throw(data.error.msg);
}
return data.result;
});
}
private handleError(error: IApiError): Observable<IUser> {
return Observable.throw(error.msg || error);
}
}

View File

@ -0,0 +1,4 @@
<div class="footer" fxLayout="row" fxLayoutWrap="wrap" ngClass.lt-md="p-15">
<div class="bottom" fxLayout="row" fxLayout.lt-md="column-reverse" fxLayoutAlign="space-between" ngClass.lt-md="lt-md">
</div>
</div>

View File

@ -0,0 +1,98 @@
@import "../../scss/variables";
@import "../../scss/mixins";
@import "../../scss/palettes";
:host {
display: block;
}
.footer {
$last-column-width: 350px;
/* min-height: 260px;*/
min-height: 130px;
padding-top: 40px;
padding-bottom: 33px;
box-sizing: border-box;
margin-top: 55px;
&.p-15 {
padding: 15px;
}
.w-100 {
width: 100%;
}
.bottom {
margin: auto;
width: 100%;
max-width: $main-content-width;
font-size: 13px;
color: fade-out(white, .2);
font-family: "Open Sans";
a{
color: white;
font-size: 20px;
}
.important {
width: $last-column-width;
}
&.lt-md {
text-align: center;
.important {
width: auto;
margin-bottom: 15px;
margin-top: 15px;
}
}
}
.top {
margin: auto;
width: 100%;
max-width: $main-content-width;
line-height: 2;
.title {
font-weight: bold;
}
.last-column {
width: $last-column-width;
}
.mt-10 {
margin-top: 10px;
}
}
.input-with-button {
border-radius: 6px;
padding-left: 15px;
background-color: transparent;
overflow: hidden;
line-height: 26px;
/deep/ {
.mat-input-wrapper {
padding-bottom: 0;
}
.mat-input-infix {
border-top: none;
}
.mat-input-underline {
display: none;
}
.mat-button {
line-height: normal;
}
}
}
}

View File

@ -0,0 +1,7 @@
import { Component } from '@angular/core';
@Component({
selector: 'knk-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss']
})
export class FooterComponent { }

View File

@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { FlexLayoutModule } from '@angular/flex-layout';
import { TranslateModule } from '@ngx-translate/core';
import { FooterComponent } from './footer.component';
import { KnkMaterialModule } from '../knk-material';
@NgModule({
imports: [KnkMaterialModule, TranslateModule, FlexLayoutModule],
declarations: [FooterComponent],
exports: [FooterComponent, KnkMaterialModule, TranslateModule, FlexLayoutModule]
})
export class FooterModule { }

View File

@ -0,0 +1,2 @@
export { FooterComponent } from './footer.component';
export { FooterModule } from './footer.module';

View File

@ -0,0 +1 @@
export * from './knk-material.module';

View File

@ -0,0 +1,86 @@
import { NgModule } from '@angular/core';
import {
MdButtonModule,
MdCardModule,
MdCheckboxModule,
MdDatepickerModule,
MdDialogModule,
MdIconModule,
MdInputModule,
MdListModule,
MdMenuModule,
MdNativeDateModule,
MdProgressBarModule,
MdProgressSpinnerModule,
MdRadioModule,
MdSelectModule,
MdSidenavModule,
MdSliderModule
// // tslint:disable-next-line:ordered-imports
// StyleModule,
// A11yModule,
// PlatformModule,
// CompatibilityModule,
// ObserveContentModule,
// MdAutocompleteModule,
// MdButtonToggleModule,
// MdChipsModule,
// MdGridListModule,
// MdRippleModule,
// MdSlideToggleModule,
// MdSnackBarModule,
// MdTabsModule,
// MdToolbarModule,
// MdTooltipModule,
// OverlayModule,
// PortalModule,
// RtlModule
} from '@angular/material';
// tslint:disable-next-line:typedef
const USED_MODULES = [
MdButtonModule,
MdCardModule,
MdCheckboxModule,
MdDatepickerModule,
MdDialogModule,
MdIconModule,
MdInputModule,
MdListModule,
MdMenuModule,
MdNativeDateModule,
MdProgressBarModule,
MdProgressSpinnerModule,
MdRadioModule,
MdSelectModule,
MdSidenavModule,
MdSliderModule
// , // tslint:disable-next-line:ordered-imports
// StyleModule,
// A11yModule,
// PlatformModule,
// CompatibilityModule,
// ObserveContentModule,
// MdAutocompleteModule,
// MdButtonToggleModule,
// MdChipsModule,
// MdGridListModule,
// MdRippleModule,
// MdSlideToggleModule,
// MdSnackBarModule,
// MdTabsModule,
// MdToolbarModule,
// MdTooltipModule,
// OverlayModule,
// PortalModule,
// RtlModule
];
@NgModule({
imports: USED_MODULES,
exports: USED_MODULES
})
export class KnkMaterialModule { }

View File

@ -0,0 +1,8 @@
<md-card class="registration-error mat-elevation-z0">
<md-card-content>
<div>
<div *ngFor="let item of items" (click)="open()">item - {{item}}</div>
</div>
</md-card-content>
</md-card>

View File

@ -0,0 +1,15 @@
import { Component, ViewContainerRef } from '@angular/core';
import { LazyDialogComponent } from './lazy.dialog';
import { MdDialog } from '@angular/material';
@Component({
templateUrl: 'lazy.component.html'
})
export class LazyComponent {
items: string[] = ['1', '2', '3', '4', '5'];
constructor(private dialog: MdDialog, private viewContainerRef: ViewContainerRef) { }
open(): void {
this.dialog.open(LazyDialogComponent, { disableClose: false, viewContainerRef: this.viewContainerRef });
}
}

View File

@ -0,0 +1,22 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { MdDialogRef } from '@angular/material';
@Component({
selector: 'knk-preview-image-dialog',
template: `
<h1 md-dialog-title>Preview document</h1>
<md-dialog-content class="centered">
Hi, I'm lazy!
</md-dialog-content>
`,
styles: ['.centered{ display: flex; justify-content:center;}']
})
export class LazyDialogComponent {
file: string;
@ViewChild('img') image: ElementRef;
constructor(public dialogRef: MdDialogRef<LazyDialogComponent>) {
setTimeout(() => {
this.dialogRef.close();
}, 5000);
}
}

View File

@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { KnkMaterialModule } from '../knk-material';
import { LazyComponent } from './lazy.component';
import { LazyDialogComponent } from './lazy.dialog';
import { LazyRoutingModule } from './lazy.routing';
@NgModule({
imports: [LazyRoutingModule, CommonModule, KnkMaterialModule],
declarations: [LazyComponent, LazyDialogComponent],
entryComponents: [LazyDialogComponent]
})
export class LazyModule { }

View File

@ -0,0 +1,14 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LazyComponent } from './lazy.component';
const routes: Routes = [
{ path: '', component: LazyComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class LazyRoutingModule { }

View File

@ -0,0 +1,2 @@
export { LoginComponent } from './login.component';
export { LoginModule } from './login.module';

View File

@ -0,0 +1,42 @@
<h1 class="heading" *ngIf="!registrationSuccess">Login</h1>
<div fxLayout="row" fxLayoutAlign="center start" fxLayoutWrap.lt-md ngClass.lt-md="lt-md">
<div fxFlex="100" fxFlex.gt-sm="730px" *ngIf="!registrationSuccess">
<md-card color="default" class="mat-elevation-z0">
<md-card-content fxLayout="row" fxLayoutWrap fxLayoutAlign="space-between">
<div fxLayout="column" fxFlex="50%" fxFlex.lt-md="100%" class="login-column" fxLayoutAlign="space-between">
<span class="login-column-title">{{'[login.enter your account data]'|translate}}</span>
<md-input-container class="full-width-input">
<input mdInput placeholder="User name" [(ngModel)]="user.userName" [type]="'text'" required/>
</md-input-container>
<md-input-container class="full-width-input">
<input mdInput placeholder="Password" [(ngModel)]="user.password" [type]="'password'" required/>
</md-input-container>
<span class="link-button" fxFlexAlign="start">{{'[login.forgot password]'|translate}}</span>
<md-checkbox color="primary">Remember me on this computer</md-checkbox>
<button md-button color="primary" (click)="login($event)">Sing in securely</button>
</div>
<!--div fxLayout="column" fxFlex="200px" class="account-column">
<span class="login-column-title">{{'[login.have not got account]'|translate}}</span>
<span class="for-borrower">For prospective borrowers &mdash; <span class="emphasis">apply for a person loan</span></span>
<span class="for-lender">For prospective lenders &mdash; <span class="emphasis">open an investment account</span> or find <span class="emphasis">more about lending</span></span>
</div-->
<div fxFlex="100" fxLayout="column" fxLayoutAlign="space-between start" class="security-section">
<div class="security-label" fxLayout="row" fxLayoutAlign="center center">
security label
</div>
<span class="login-column-title">Security</span>
<span>&lt;Details about security&gt;</span>
</div>
</md-card-content>
</md-card>
</div>
<!--div fxFlex.lt-md="100" fxFlexOffset.gt-sm="28px" class="general-info" ngClass.gt-sm="gt-sm">
<md-card color="primary" class="mat-elevation-z0">
<md-list>
<md-list-item fxLayour="row" ><md-icon color="primary">local_phone</md-icon>&nbsp;<span [innerHTML]="'[help.info.item.1]'|translate"></span></md-list-item>
<md-divider></md-divider>
<md-list-item fxLayour="row" ><md-icon color="primary">verified_user</md-icon>&nbsp;<span>{{'[help.info.item.2]'|translate}}</span></md-list-item>
</md-list>
</md-card>
</div-->
</div>

View File

@ -0,0 +1,56 @@
@import "../../scss/variables";
@import "../../scss/mixins";
@import "../../scss/palettes";
// @import "../../scss/lendoit-theme
// @include lendoit-theme($lendoit-app-theme);
:host {
margin: auto;
display: inline-block;
.login-column-title {
text-align: left;
font-weight: bold;
margin-bottom: 15px;
}
.account-column {
text-align: left;
.for-borrower,
.for-lender {
margin-bottom: 20px;
}
}
.lt-md {
.account-column {
margin-top: 20px;
}
.general-info {
margin-top: 20px;
}
}
md-checkbox {
margin-top: 15px;
margin-bottom: 15px;
text-align: left;
}
.emphasis {
text-decoration: underline;
font-weight: bold;
}
.security-section {
margin-top: 35px;
.security-label {
width: 75px;
height: 75px;
margin-bottom: 15px;
}
}
}

View File

@ -0,0 +1,27 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { TokenService, UserCredentials } from '../core/shared';
@Component({
selector: 'knk-login',
styleUrls: ['./login.component.scss'],
templateUrl: 'login.component.html'
})
export class LoginComponent implements OnInit {
user: UserCredentials;
constructor(private router: Router,
private route: ActivatedRoute,
private tokenService: TokenService) {
this.user = new UserCredentials();
}
ngOnInit(): void {
console.log('init');
}
login(): void {
this.tokenService.apiUserToken(this.user).subscribe(() => {
this.router.navigate(['..', 'dashboard'], { relativeTo: this.route.parent });
});
}
}

View File

@ -0,0 +1,16 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { KnkMaterialModule } from '../knk-material';
import { FlexLayoutModule } from '@angular/flex-layout';
import { TranslateModule } from '@ngx-translate/core';
import { LoginComponent } from './login.component';
import { LoginRoutingModule } from './login.routing';
@NgModule({
imports: [LoginRoutingModule, KnkMaterialModule, FormsModule, CommonModule, TranslateModule, FlexLayoutModule],
declarations: [LoginComponent]
})
export class LoginModule { }

View File

@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './login.component';
import { ApiConsumerTokenResolve, UserTokenResolve } from '../core/shared';
export const loginRoutes: Routes = [
{
path: '',
component: LoginComponent,
resolve: {
token: ApiConsumerTokenResolve,
auth: UserTokenResolve
}
}
];
@NgModule({
imports: [RouterModule.forChild(loginRoutes)],
exports: [RouterModule]
})
export class LoginRoutingModule { }

View File

@ -0,0 +1,79 @@
<h1 class="heading">My profile</h1>
<div fxLayout="row" fxLayoutAlign="center start" fxLayoutWrap.lt-md ngClass.lt-md="lt-md" *ngIf="user">
<div fxFlex="100" fxFlex.gt-sm="730px">
<md-card color="default" class="mat-elevation-z0">
<md-card-content fxLayout="row" fxLayoutWrap fxLayoutAlign="space-between">
<md-input-container class="userName" floatPlaceholder="never" fxFlex="65%" fxFlex.lt-md="100%">
<input readonly mdInput placeholder="User Name" [value]="user.firstName+' '+user.lastName">
<span mdSuffix>(#{{user?.userId}}-{{accounts[0]?.accountId}})</span>
</md-input-container>
<md-input-container fxFlex="45%" *ngIf="!edit" fxFlex.lt-md="100%" (click)="toggleEditMode(true)">
<input mdInput readonly="true" placeholder="Email address" [(ngModel)]="user.email">
</md-input-container>
<md-input-container fxFlex="45%" *ngIf="!edit" fxFlex.lt-md="100%" (click)="toggleEditMode(true)">
<input mdInput readonly="true" placeholder="Mobile phone" [(ngModel)]="user.phone">
</md-input-container>
<md-input-container fxFlex="45%" *ngIf="edit" fxFlex.lt-md="100%">
<input mdInput placeholder="Email address" [(ngModel)]="editUser.email">
</md-input-container>
<md-input-container fxFlex="45%" *ngIf="edit" fxFlex.lt-md="100%">
<input mdInput placeholder="Mobile phone" [(ngModel)]="editUser.phone">
</md-input-container>
<md-input-container fxFlex="100%" *ngIf="!edit" (click)="toggleEditMode(true)">
<input mdInput readonly placeholder="Address" [value]="user.address+', '+user.city+', '+user.state+', '+user.postCode">
</md-input-container>
<md-input-container fxFlex.lt-md="100%" fxFlex="45%" *ngIf="edit">
<input mdInput placeholder="Address" [(ngModel)]="editUser.address">
</md-input-container>
<md-input-container fxFlex.lt-md="100%" fxFlex="45%" *ngIf="edit">
<input mdInput placeholder="City" [(ngModel)]="editUser.city">
</md-input-container>
<md-input-container fxFlex.lt-md="100%" fxFlex="45%" *ngIf="edit">
<input mdInput placeholder="State" [(ngModel)]="editUser.state">
</md-input-container>
<md-input-container fxFlex.lt-md="100%" fxFlex="45%" *ngIf="edit">
<input mdInput placeholder="Post code" [(ngModel)]="editUser.postCode">
</md-input-container>
<h3 fxFlex="100%" *ngIf="edit">Please enter your password to confirm changes</h3>
<md-input-container fxFlex.lt-md="100%" fxFlex="50%" [style.display]="!edit?'none':''" [style.margin-bottom]="edit?'30px':''">
<input #pwdInput type="Password" mdInput placeholder="password" [(ngModel)]="editUser.password">
</md-input-container><span fxFlex="1" class="separator" *ngIf="edit"></span>
<md-select fxFlex="50%" fxFlex.lt-md="100%" *ngIf="!edit" [disabled]="true" (click)="toggleEditMode(true)" md-no-asterisk placeholder="The interface language"
class="full-width-input mat-primary" [(ngModel)]="user.languageCode" required no-asterisk>
<md-option *ngFor="let option of languages" [value]="option.id" class="mat-primary">{{option.value}}</md-option>
</md-select>
<md-select fxFlex="50%" fxFlex.lt-md="100%" *ngIf="edit" (click)="toggleEditMode(true)" md-no-asterisk placeholder="The interface language"
class="full-width-input mat-primary" [(ngModel)]="editUser.languageCode" required no-asterisk>
<md-option *ngFor="let option of languages" [value]="option.id" class="mat-primary">{{option.value}}</md-option>
</md-select>
<div fxFlex="100%" class="wide-radio" fxLayout="row" fxLayoutAlign="start baseline" (click)="toggleEditMode(true)">
<span fxFlex="50" class="wide-radio-items">{{("[property name.Would you like to get our newsletter]"|translate)}}</span>
<md-radio-group fxFlex [(ngModel)]="user.newsletterApproval" class="wide-radio-items">
<md-radio-button [value]="true" aria-label="Yes" class="mat-accent">Yes</md-radio-button>
<md-radio-button [value]="false" aria-label="No" class="mat-accent">No</md-radio-button>
</md-radio-group>
</div>
<div fxFlex="100%" fxLayout="row" fxLayoutAlign.gt-sm="end" class="buttons">
<div fxFlex.lt-md="100%" fxLayout="row-reverse" fxFlex="50%" fxLayoutAlign="space-between baseline">
<button fxFlex="50" md-button color="accent" (click)="save()" [disabled]="!edit || pwdInput==null || pwdInput.value.length == 0">{{'[myaccaunt.save]'|translate}}</button>
<span *ngIf="edit">or</span>
<span class="link-button" (click)="toggleEditMode(false)" *ngIf="edit">Cancel</span>
</div>
</div>
</md-card-content>
</md-card>
</div>
<div fxFlex.lt-md="100" fxFlexOffset.gt-sm="28px" class="general-info" ngClass.gt-sm="gt-sm">
<md-card color="primary" class="mat-elevation-z0">
<md-list>
<md-list-item fxLayour="row" ><md-icon color="primary">local_phone</md-icon>&nbsp;<span [innerHTML]="'[help.info.item.1]'|translate"></span></md-list-item>
<md-divider></md-divider>
<md-list-item fxLayour="row" ><md-icon color="primary">verified_user</md-icon>&nbsp;<span>{{'[help.info.item.2]'|translate}}</span></md-list-item>
</md-list>
</md-card>
</div>
</div>

View File

@ -0,0 +1,42 @@
@import "../../scss/variables";
@import "../../scss/mixins";
@import "../../scss/palettes";
:host {
text-align: left;
.userName {
input {
font-size: 30px;
}
}
.wide-radio {
margin-top: 20px;
.wide-radio-items {
margin-right: 10px;
&[role="radiogroup"] {
margin-left: 10px;
md-radio-button {
margin-right: 10px;
}
}
}
}
.separator {
margin-bottom: 10px;
}
[color="accent"] {
padding-left: 15px;
padding-right: 15px;
}
.buttons {
margin-top: 20px;
}
}

Some files were not shown because too many files have changed in this diff Show More