In this StackBlitz I have a Kendo for Angular grid. When you click on the button, the second row will be selected after half second, and unselected automatically after two seconds.
What I need is the selected row to fade-in on selection and fade-out after two seconds, is this possible?
#Component({
selector: 'my-app',
template: `
<button type="button" (click)="select()">Select</button>
<kendo-grid [data]="gridData" [height]="410"
kendoGridSelectBy="ProductID" [(selectedKeys)]="selection">
<kendo-grid-column field="ProductID" title="ID" width="40">
</kendo-grid-column>
<kendo-grid-column field="ProductName" title="Name" width="250">
</kendo-grid-column>
</kendo-grid>
`
})
export class AppComponent {
selection: number[] = [];
public gridData: any[] = products;
select(){
setTimeout(() => {
this.selection = [2];
setTimeout(() => {
this.selection = [];
}, 2000);
}, 500);
}
}
Not sure if this is the most optimized solution, but you can use:
rowClass (https://www.telerik.com/kendo-angular-ui/components/grid/api/GridComponent/#toc-rowclass)
selectionChange (https://www.telerik.com/kendo-angular-ui/components/grid/api/GridComponent/#toc-selectionchange)
With that function and event, you can add a custom class to your selected rows and use CSS for the fade animation. Your code would be something like this:
import { Component } from '#angular/core';
import { products } from './products';
import { Component, ViewEncapsulation } from '#angular/core';
import { RowClassArgs } from '#progress/kendo-angular-grid';
#Component({
selector: 'my-app',
encapsulation: ViewEncapsulation.None,
styles: [`
.k-grid tr.isSelected {
background-color: #41f4df;
transition: background-color 1s linear;
}
.k-grid tr.isNotSelected {
background-color: transparent;
transition: background-color 2s linear;
}
`],
template: `
<kendo-grid [data]="gridData"
[height]="410"
kendoGridSelectBy="ProductID"
[rowClass]="rowCallback"
(selectionChange)="onSelect($event)">
<kendo-grid-column field="ProductID" title="ID" width="40">
</kendo-grid-column>
<kendo-grid-column field="ProductName" title="Name" width="250">
</kendo-grid-column>
</kendo-grid>
`
})
export class AppComponent {
public gridData: any[] = products;
public onSelect(e){
setTimeout(() => {
e.selectedRows[0].dataItem.isSelected = true;
setTimeout(() => {
e.selectedRows[0].dataItem.isSelected = false;
}, 2000);
}, 500);
}
public rowCallback(context: RowClassArgs) {
if (context.dataItem.isSelected){
return {
isSelected: true,
};
} else {
return {isNotSelected: true};
}
}
}
-- EDIT --
Just noticed that you want to do that only with the second row. In that case, you can replace line e.selectedRows[0].dataItem.isSelected = true; with: products[1].isSelected = true;.
And use your button to call the onSelect function.
Related
I am transferring my CRA to a Nextjs and I am having a bit of an issue with anything that uses the <canvas> element. The charts and data are mostly there, but my annotations are now missing from the charts. I have tried importing everything with the dynamic function for the parent element, but it still seems to not show the missing features.
I am also seeing some weird things happening on an arcgis map which is not visualizing 3d elements on a <canvas>. So my guess is that this has something with the way that canvas or d3 interact with the browser.
// parent component
import moment from 'moment-timezone';
import React, { useRef } from 'react';
import {
Chart as ChartJS,
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip,
} from 'chart.js';
// import { Chart } from 'react-chartjs-2';
import { ArrowRight } from '../../icons/ArrowRight';
import Link from 'next/link';
import { chartOptions } from '../../lib/chartOptions';
import dynamic from 'next/dynamic';
const Chart = dynamic((): any => import('react-chartjs-2').then((m: any) => m.Chart), {
ssr: false,
});
ChartJS.register(
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip,
);
export const MarkupCard = ({ item }: any) => {
const chartRef = useRef();
const userName = item.user_id.split('#')[0];
return (
<div className="w-2/3 pb-10 mx-auto border-2 border-blue items-center rounded-lg my-4 py-4 flex flex-row justify-between">
<div className="w-full text-left pl-4 pb-6 h-72">
<div className="w-full flex flex-row justify-between">
<h2 className="text-lg font-bold">{userName} Marked up a chart</h2>
<div className=" w-1/3 text-right pr-4">
<h2>
{moment(item.created_at)
.tz(process.env.NEXT_PUBLIC_TIMEZONE ?? '')
.format('MM-DD-YYYY hh:mm:ss a')}
</h2>
</div>
</div>
<h2>Route: {item.routeLongName}</h2>
<Chart
style={{ height: '100px', width: '99%' }}
ref={chartRef}
plugins={item.details.options.plugins}
className="trips-chart"
type="line"
options={chartOptions(item.details.options, item.details.annotations)}
data={item.details.chartData}
/>
</div>
<Link href={`/app/markupDetail/${item.id}`}>
<button className="mx-6 h-full flex">
<ArrowRight />
</button>
</Link>
</div>
);
};
// chart component
import React, { useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { supabase } from '../../client';
import { actions } from '../../store';
import { SocialNote } from '../../types';
import { Card } from './Card';
const SocialFeed = () => {
const [feed, setFeed] = useState<SocialNote[]>([]);
const dispatch = useDispatch();
const loadPage = async () => {
dispatch(actions.setLoaded(true));
const { data, error } = await supabase
.from('notes')
.select('*')
.order('last_update', { ascending: false });
if (data) {
setFeed(data);
console.log(data);
return data;
} else {
return error;
}
};
useEffect((): (() => void) => {
loadPage();
return () => supabase.removeAllSubscriptions();
}, []);
return (
<div className="w-full mx-auto overflow-y-auto">
{feed.map((item, key) => (
<Card key={key} item={item} />
))}
</div>
);
};
export default SocialFeed;
// chartoptions.js
export const chartOptions: any = (options: any, annotations: any) => {
const { title } = options;
const { tooltip } = options.plugins;
return {
title,
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index' as const,
intersect: false,
},
plugins: {
annotation: { annotations: annotations },
tooltip,
legend: {
position: 'top' as const,
},
title,
},
};
};
// next.config.js
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig
CRA
Nextjs
Turns out I just needed to register Annotations in ChartJS.register()
This was not the case for React, but was required for Nextjs
import Annotation from 'chartjs-plugin-annotation';
ChartJS.register(
Annotation,
LinearScale,
CategoryScale,
BarElement,
PointElement,
LineElement,
Legend,
Tooltip,
);
I want to change the text color in a kendo-numerictextbox for KendoUI for Angular.
The style attribute has no effect. With Jquery we can change the style but how can we do this in KendoUI for Angular ?
<kendo-numerictextbox class="form-control"
[decimals]="1"
[spinners]="false"
[format]="'n1'"
tabindex="{{i}}"
style="font-size:12px; padding:1px; color:red"
[formControlName]="item.Index" >
</kendo-numerictextbox>
import { Component } from "#angular/core";
#Component({
selector: "my-app",
template: `
<style>
::ng-deep .k-numerictextbox .k-numeric-wrap .k-input {
color: red;
}
</style>
<kendo-numerictextbox
[value]="value"
[min]="0"
[max]="100"
[autoCorrect]="autoCorrect"
style="font-size:12px; padding:1px;"
>
</kendo-numerictextbox>
`
})
export class AppComponent {
public autoCorrect: boolean = false;
public value: number = 5;
}
I am trying to execute kendo ui multiselect some what like code below.But i need to change the background color of selected item from red color to some other color.I tried giving custom css in popupsettings but still it is not working.
Most googled answers are in jquery but i need it in angular 4.Could you please help me out
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
template: `
<p>Custom values are <strong>enabled</strong>. Type a custom value.</p>
<p>primitive data</p>
<div class="example-wrapper">
<kendo-multiselect
[data]="sizes"
[value]="selectedSizes"
[allowCustom]="true"
(valueChange)="onSizeChange($event)"
>
<ng-template kendoMultiSelectCustomItemTemplate let-customText>
<strong>{{ customText }}</strong>
</ng-template>
</kendo-multiselect>
</div>
`
})
export class AppComponent {
public sizes: Array<string> = [ "Small", "Medium", "Large" ];
public selectedSizes: Array<string> = [];
public onSizeChange(value) {
this.selectedSizes = value;
}
}
Add the following CSS to your component:
::ng-deep .k-item.k-state-selected {
background-color: green !important;
}
This is kendo solution.
:host ::ng-deep .k-state-selected {
background-color: green !important;
}
url: https://www.progress.com/blogs/options-for-styling-components-in-kendo-ui-builder
I want my table to refresh on save click, it's working for normal table with *ngFor, but I'm using Smartadmin angular template. I think the solution may be related to table.ajax.reload() , but how do i execute this in angular way.
save-tax.component.ts
import { Component, OnInit, Input ,Output, EventEmitter } from '#angular/core';
// Forms related packages
import { FormBuilder, FormGroup, Validators } from '#angular/forms';
import { FormValidation } from 'app/shared/validators/formValidation'; //custom form validation
import { ConfigurationService } from 'app/+configuration/configuration.service';
import { FlashMessagesService } from 'angular2-flash-messages';
#Component({
selector: 'save-tax',
templateUrl: './save-tax.component.html'
})
export class SaveTaxComponent implements OnInit {
#Output() reloadTableData = new EventEmitter();
saveTaxForm: FormGroup;
constructor(private _fb: FormBuilder,
private _config: ConfigurationService,
private _flash: FlashMessagesService) { }
ngOnInit() {
this.saveTaxForm_builder();
}
saveTaxForm_builder() {
this.saveTaxForm = this._fb.group({
tax_title: [null, [
Validators.required
]],
tax_rate: [null, [
Validators.required,
Validators.pattern(FormValidation.patterns().price),
]],
});
}
tTitle = "input"; tRate = "input";
validInput(val) {
var classSuccess = "input state-success";
val == 'tax_title' ? this.tTitle = classSuccess : null;
val == 'tax_rate' ? this.tRate = classSuccess : null;
}
invalidInput(val) {
var classError = "input state-error";
val == 'tax_title' ? this.tTitle = classError : null;
val == 'tax_rate' ? this.tRate = classError : null;
}
classReset() {
this.tTitle = "input";
this.tRate = "input";
}
save_tax() {
if (this.saveTaxForm.value) {
this._config.createTax(this.saveTaxForm.value).subscribe(data => {
if (data.success) {
this._flash.show(data.msg, { cssClass: 'alert alert-block alert-success', timeout: 1000 });
this.saveTaxForm.reset();
this.classReset();
this.reloadTableData.emit(); // Emitting an event
} else {
this.saveTaxForm.reset();
this.classReset();
this._flash.show(data.msg, { cssClass: 'alert alert-block alert-danger', timeout: 3500 });
}
},
error => {
this.saveTaxForm.reset();
this.classReset();
this._flash.show("Please contact customer support. " + error.status + ": Internal server error.", { cssClass: 'alert alert-danger', timeout: 5000 });
});
} else {
this._flash.show('Something went wrong! Please try again..', { cssClass: 'alert alert-warning', timeout: 3000 });
}
}
}
datatable.component.ts
import {Component, Input, ElementRef, AfterContentInit, OnInit} from '#angular/core';
declare var $: any;
#Component({
selector: 'sa-datatable',
template: `
<table class="dataTable responsive {{tableClass}}" width="{{width}}">
<ng-content></ng-content>
</table>
`,
styles: [
require('smartadmin-plugins/datatables/datatables.min.css')
]
})
export class DatatableComponent implements OnInit {
#Input() public options:any;
#Input() public filter:any;
#Input() public detailsFormat:any;
#Input() public paginationLength: boolean;
#Input() public columnsHide: boolean;
#Input() public tableClass: string;
#Input() public width: string = '100%';
constructor(private el: ElementRef) {
}
ngOnInit() {
Promise.all([
System.import('script-loader!smartadmin-plugins/datatables/datatables.min.js'),
]).then(()=>{
this.render()
})
}
render() {
let element = $(this.el.nativeElement.children[0]);
let options = this.options || {}
let toolbar = '';
if (options.buttons)
toolbar += 'B';
if (this.paginationLength)
toolbar += 'l';
if (this.columnsHide)
toolbar += 'C';
if (typeof options.ajax === 'string') {
let url = options.ajax;
options.ajax = {
url: url,
complete: function (xhr) {
options.ajax.reload();
}
}
}
options = $.extend(options, {
"dom": "<'dt-toolbar'<'col-xs-12 col-sm-6'f><'col-sm-6 col-xs-12 hidden-xs text-right'" + toolbar + ">r>" +
"t" +
"<'dt-toolbar-footer'<'col-sm-6 col-xs-12 hidden-xs'i><'col-xs-12 col-sm-6'p>>",
oLanguage: {
"sSearch": "<span class='input-group-addon'><i class='glyphicon glyphicon-search'></i></span> ",
"sLengthMenu": "_MENU_"
},
"autoWidth": false,
retrieve: true,
responsive: true,
initComplete: (settings, json)=> {
element.parent().find('.input-sm', ).removeClass("input-sm").addClass('input-md');
}
});
const _dataTable = element.DataTable(options);
if (this.filter) {
// Apply the filter
element.on('keyup change', 'thead th input[type=text]', function () {
_dataTable
.column($(this).parent().index() + ':visible')
.search(this.value)
.draw();
});
}
//custom functions
element.on('click', 'delete', function () {
var tr = $(this).closest('tr');
var row = _dataTable.row( tr );
if ( $(this).hasClass('delete') ) {
row.remove().draw(false);
console.log(row);
}
else {
//$(table).$('tr.selected').removeClass('selected');
$(this).addClass('selected');
}
console.log($(this).attr("class"))
});
//end custom functions
if (!toolbar) {
element.parent().find(".dt-toolbar").append('<div class="text-right"><img src="assets/img/logo.png" alt="SmartAdmin" style="width: 111px; margin-top: 3px; margin-right: 10px;"></div>');
}
if(this.detailsFormat){
let format = this.detailsFormat
element.on('click', 'td.details-control', function () {
var tr = $(this).closest('tr');
var row = _dataTable.row( tr );
if ( row.child.isShown() ) {
row.child.hide();
tr.removeClass('shown');
}
else {
row.child( format(row.data()) ).show();
tr.addClass('shown');
}
})
}
}
}
tax-list.component.html
<!-- NEW COL START -->
<article class="col-sm-12 col-md-12 col-lg-12">
<!-- Widget ID (each widget will need unique ID)-->
<div sa-widget [editbutton]="false" [custombutton]="false">
<header>
<span class="widget-icon"> <i class="fa fa-percent"></i> </span>
<h2>Tax Rule List</h2>
</header>
<!-- widget div-->
<div>
<!-- widget content -->
<div class="widget-body no-padding">
<sa-datatable [options]="tableData" paginationLength="true" tableClass="table table-striped table-bordered table-hover" width="100%">
<thead>
<tr>
<th data-hide="phone"> ID </th>
<th data-hide="phone,tablet">Tax Title</th>
<th data-class="expand">Tax Rate</th>
<th data-hide="phone,tablet">Status</th>
<th data-hide="phone,tablet"> Action </th>
</tr>
</thead>
</sa-datatable>
</div>
<!-- end widget content -->
</div>
<!-- end widget div -->
</div>
<!-- end widget -->
</article>
<!-- END COL -->
tax-list.component.ts
import { FlashMessagesService } from 'angular2-flash-messages';
import { ConfigurationService } from 'app/+configuration/configuration.service';
import { Component, OnInit } from '#angular/core';
declare var $: any;
#Component({
selector: 'tax-list',
templateUrl: './tax-list.component.html'
})
export class TaxListComponent implements OnInit {
tableData: any;
constructor(private _config: ConfigurationService, private _flash: FlashMessagesService) { }
ngOnInit() {
this.fetchTableData();
this.buttonEvents();
}
fetchTableData() {
this.tableData = {
ajax: (data, callback, settings) => {
this._config.getTaxRules().subscribe(data => {
if (data.success) {
callback({
aaData: data.data
});
} else {
alert(data.msg);
}
},
error => {
alert('Internal server error..check database connection.');
});
},
serverSIde:true,
columns: [
{
render: function (data, type, row, meta) {
return meta.row + 1;
}
},
{ data: 'tax_title' },
{ data: 'tax_rate' },
{ data: 'status' },
{
render: function (data, type, row) {
return `<button type="button" class="btn btn-warning btn-xs edit" data-element-id="${row._id}">
<i class="fa fa-pencil-square-o"></i> Edit</button>
<button type="button" class="btn btn-danger btn-xs delete" data-element-id="${row._id}">
<i class="fa fa-pencil-square-o"></i> Delete</button>`;
}
}
],
buttons: [
'copy', 'pdf', 'print'
]
};
}
buttonEvents(){
document.querySelector('body').addEventListener('click', (event) => {
let target = <Element>event.target; // Cast EventTarget into an Element
if (target.tagName.toLowerCase() === 'button' && $(target).hasClass('edit')) {
this.tax_edit(target.getAttribute('data-element-id'));
}
if (target.tagName.toLowerCase() === 'button' && $(target).hasClass('delete')) {
this.tax_delete(target.getAttribute('data-element-id'));
}
});
}
tax_edit(tax_id) {
}
tax_delete(tax_id) {
this._config.deleteTaxById(tax_id).subscribe(data => {
if (data.success) {
this._flash.show(data.msg, { cssClass: 'alert alert-info fade in', timeout: 3000 });
this.fetchTableData();
} else {
this._flash.show(data.msg, { cssClass: 'alert alert-warning fade in', timeout: 3000 });
}
},
error => {
this._flash.show(error, { cssClass: 'alert alert-warning fade in', timeout: 3000 });
});
}
reloadTable(){
this.ngOnInit();
}
}
You can add a refresh button in your widget using <div class='widget-toolbar'>...</div> and using (click) event binding, attach a method with it. I named it as onRefresh() ...
<div sa-widget [editbutton]="false" [colorbutton]="false">
<header>
<span class="widget-icon">
<i class="fa fa-chart"></i>
</span>
<h2>Sample Datatable</h2>
<div class="widget-toolbar" role="menu">
<a class="glyphicon glyphicon-refresh" (click)="onRefresh('#studentTable table')"></a>
</div>
</header>
<div>
<div class="widget-body no-padding">
<sa-datatable id="studentTable" [options]="datatableOptions" tableClass="table table-striped table-bordered table-hover table-responsive">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Rank</th>
<th>Options</th>
</tr>
</thead>
</sa-datatable>
</div>
</div>
</div>
Focus at the method which I have given in the click event binding, I passed the id of the <sa-datatable> with table, that is #studentTable table that mentions the table tag according to the datatable implementation of the smartadmin.
Now in the component, add a method 'onRefresh()' which should be like
onRefresh(id: any) {
if ($.fn.DataTable.isDataTable(id)) {
const table = $(id).DataTable();
table.ajax.reload();
}
}
In this method, that #studentTable table will come in this id which is a parameter in the method. Using jQuery you can do the table.ajax.reload().
But you need to declare the jQuery at the top.
declare var $ : any;
app.component.ts
import { Component, OnInit, OnDestroy } from '#angular/core';
declare var $: any;
#Component({
selector: 'app-order',
templateUrl: './order.component.html',
styleUrls: ['./order.component.css']
})
export class OrderComponent implements OnInit, OnDestroy {
datatableOptions:{
...
}
constructor(){
...
}
ngOnInit(){
...
}
onRefresh(){
if ($.fn.DataTable.isDataTable(id)) {
const table = $(id).DataTable();
table.ajax.reload();
}
}
}
How to enable the circular progress when user clicks on submit on the login page? I can able to see the loader symbol in the app bar on other pages but I'm not able to activate it on the login page.
We need to add Custom reducer for login page. I did it in the following way.
1.1. Create a new login page. Just copy and paste the admin-on-rest login page code.
1.2. Update the propTypes like below
Login.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object.isRequired,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
isLogging: PropTypes.bool.isRequired,
};
1.3. Add the below line
function mapStateToProps(state, props) {
return {
isLogging: state.loginReducer > 0
};
}
1.4. Update the login page with below code.
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.username) errors.username = translate('aor.validation.required');
if (!values.password) errors.password = translate('aor.validation.required');
return errors;
},
}),
connect(mapStateToProps, { userLogin: userLoginAction }),
);
export default enhance(Login);
1.5. Replace the submit button code
<CardActions>
<RaisedButton type="submit" primary disabled={isLogging} icon={isLogging && <CircularProgress size={25} thickness={2} />} label={translate('aor.auth.sign_in')} fullWidth />
</CardActions>
1.6 The complete code for the login page is
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { propTypes, reduxForm, Field } from 'redux-form';
import { connect } from 'react-redux';
import compose from 'recompose/compose';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import { Card, CardActions } from 'material-ui/Card';
import Avatar from 'material-ui/Avatar';
import RaisedButton from 'material-ui/RaisedButton';
import TextField from 'material-ui/TextField';
import CircularProgress from 'material-ui/CircularProgress';
import { cyan500, pinkA200, white } from 'material-ui/styles/colors';
import defaultTheme, {translate, Notification, userLogin as userLoginAction } from 'admin-on-rest';
const styles = {
main: {
display: 'flex',
flexDirection: 'column',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
},
card: {
minWidth: 300,
},
avatar: {
margin: '1em',
textAlign: 'center ',
},
avatarText:{
verticalAlign:'middle',
fontSize:20,
},
form: {
padding: '0 1em 1em 1em',
},
input: {
display: 'flex',
},
};
function getColorsFromTheme(theme) {
if (!theme) return { primary1Color: cyan500, accent1Color: pinkA200 };
const {
palette: {
primary1Color,
accent1Color,
},
} = theme;
return { primary1Color, accent1Color };
}
// see http://redux-form.com/6.4.3/examples/material-ui/
const renderInput = ({ meta: { touched, error } = {}, input: { ...inputProps }, ...props }) =>
<TextField
errorText={touched && error}
{...inputProps}
{...props}
fullWidth
/>;
class Login extends Component {
login = (auth) => this.props.userLogin(auth, this.props.location.state ? this.props.location.state.nextPathname : '/');
render() {
const { handleSubmit, submitting, theme, translate, isLogging } = this.props;
const muiTheme = getMuiTheme(theme);
const { primary1Color } = getColorsFromTheme(muiTheme);
return (
<MuiThemeProvider muiTheme={muiTheme}>
<div style={{ ...styles.main, backgroundColor: primary1Color }}>
<Card style={styles.card}>
<div style={styles.avatar}>
<div>
<Avatar backgroundColor={white} src="EnsembleGreenLogo.png" size={45} />
</div>
<div>
<span style={styles.avatarText}>Ensemble SmartWAN Manager</span>
</div>
</div>
<form onSubmit={handleSubmit(this.login)}>
<div style={styles.form}>
<div style={styles.input} >
<Field
name="username"
component={renderInput}
floatingLabelText={translate('aor.auth.username')}
disabled={submitting}
/>
</div>
<div style={styles.input}>
<Field
name="password"
component={renderInput}
floatingLabelText={translate('aor.auth.password')}
type="password"
disabled={submitting}
/>
</div>
</div>
<CardActions>
<RaisedButton
type="submit"
primary
disabled={isLogging}
icon={isLogging && <CircularProgress size={25} thickness={2} />}
label={translate('aor.auth.sign_in')}
fullWidth
/>
</CardActions>
</form>
</Card>
<Notification />
</div>
</MuiThemeProvider>
);
}
}
Login.propTypes = {
...propTypes,
authClient: PropTypes.func,
previousRoute: PropTypes.string,
theme: PropTypes.object.isRequired,
translate: PropTypes.func.isRequired,
userLogin: PropTypes.func.isRequired,
isLogging: PropTypes.bool.isRequired,
};
Login.defaultProps = {
theme: defaultTheme,
};
function mapStateToProps(state, props) {
return {
isLogging: state.loginReducer > 0
};
}
const enhance = compose(
translate,
reduxForm({
form: 'signIn',
validate: (values, props) => {
const errors = {};
const { translate } = props;
if (!values.username) errors.username = translate('aor.validation.required');
if (!values.password) errors.password = translate('aor.validation.required');
return errors;
},
}),
connect(mapStateToProps, { userLogin: userLoginAction }),
);
export default enhance(Login);
2.1. Add a new file (src/loginReducer.js) in src folder with the below content
import { USER_LOGIN_LOADING, USER_LOGIN_SUCCESS, USER_LOGIN_FAILURE, USER_CHECK } from 'admin-on-rest';
export default (previousState = 0, { type }) => {
switch (type) {
case USER_LOGIN_LOADING:
return previousState + 1;
case USER_LOGIN_SUCCESS:
case USER_LOGIN_FAILURE:
case USER_CHECK:
return Math.max(previousState - 1, 0);
default:
return previousState;
}
};
3.1 Update the app.js admin tag.
<Admin
menu={createMenus}
loginPage={Login}
dashboard={Dashboard}
appLayout={Layout}
customReducers={{ loginReducer }}
>
3.2 import the login page and login reducers in app.js
import loginReducer from './loginReducer';
import Login from "./Login";