Compare commits

...

19 Commits

Author SHA1 Message Date
a.bouhuolia
883c5dcb41 Merge branch 'signup-restrictions' into develop 2023-05-08 00:36:50 +02:00
a.bouhuolia
be10b8934d fix(webapp): change the error code handler 2023-05-08 00:35:44 +02:00
a.bouhuolia
ce38c71fa7 fix(server): should allowed email addresses and domain be irrespective. 2023-05-08 00:35:28 +02:00
Ahmed Bouhuolia
1162fbc7c3 Merge pull request #117 from bigcapitalhq/signup-restrictions
Sign-up restrictions for self-hosted
2023-05-08 00:18:56 +02:00
a.bouhuolia
18b9e25f2b chore: update .env.example 2023-05-07 23:59:41 +02:00
a.bouhuolia
dd26bdc482 feat(webapp): sign-up restrictions 2023-05-07 23:54:42 +02:00
a.bouhuolia
ad3c9ebfe9 feat(server): sign-up restrictions for self-hosted 2023-05-07 17:22:18 +02:00
a.bouhuolia
36611652da fix(webapp): resource meta of vendors list 2023-05-05 15:41:32 +02:00
a.bouhuolia
06c7ee71b4 fix(webapp): display transactions count in cashflow account 2023-05-05 13:54:45 +02:00
Ahmed Bouhuolia
54d3188666 Merge pull request #116 from bigcapitalhq/BIG-427-fix-sending-invite-email
fix(server): sending invite email
2023-05-05 00:30:24 +02:00
a.bouhuolia
3ceb9adda2 fix(server): sending invite email 2023-05-05 00:28:57 +02:00
Ahmed Bouhuolia
1249415054 Merge pull request #115 from bigcapitalhq/BIG-409-some-flag-icons-are-missing
fix(webapp): some flag icons are missing
2023-05-04 21:32:10 +02:00
Ahmed Bouhuolia
6c96c371c5 Merge pull request #114 from bigcapitalhq/BIG-279-select-specific-accounts-in-general-ledger-does-not-working
`BIG-279` Select specific accounts in general ledger does not working.
2023-05-04 14:29:35 +02:00
a.bouhuolia
6c61a69f10 feat(webapp): handle create item on Accounts select components 2023-05-04 14:24:45 +02:00
a.bouhuolia
981b65349d feat(webapp): allow to create a new account item in accounts list component. 2023-05-03 22:41:54 +02:00
a.bouhuolia
a7d29a31c8 refactor(webapp): all services with new AccountSelect and AccountMultiSelect components. 2023-05-01 00:13:23 +02:00
a.bouhuolia
c1d92b74f0 chore(Select):style the Select button. 2023-04-30 21:13:33 +02:00
a.bouhuolia
6f0f47f38a refactor(webapp): Accounts Select and MultiSelect components 2023-04-30 17:33:15 +02:00
a.bouhuolia
83510cfa70 feat(server): add structure query flat or tree to accounts chart endpoint 2023-04-30 17:24:49 +02:00
73 changed files with 1345 additions and 1142 deletions

View File

@@ -32,3 +32,8 @@ CONTACT_US_MAIL=support@bigcapital.ly
# Agendash
AGENDASH_AUTH_USER=agendash
AGENDASH_AUTH_PASSWORD=123123
# Sign-up restrictions
SIGNUP_DISABLED=true
SIGNUP_ALLOWED_DOMAINS=
SIGNUP_ALLOWED_EMAILS=

View File

@@ -72,6 +72,11 @@ services:
- AGENDASH_AUTH_USER=${AGENDASH_AUTH_USER}
- AGENDASH_AUTH_PASSWORD=${AGENDASH_AUTH_PASSWORD}
# Sign-up restrictions
- SIGNUP_DISABLED=${SIGNUP_DISABLED}
- SIGNUP_ALLOWED_DOMAINS=${SIGNUP_ALLOWED_DOMAINS}
- SIGNUP_ALLOWED_EMAILS=${SIGNUP_ALLOWED_EMAILS}
database_migration:
container_name: bigcapital-database-migration
build:

View File

@@ -34,7 +34,11 @@ ARG MAIL_HOST= \
BASE_URL= \
# Agendash
AGENDASH_AUTH_USER=agendash \
AGENDASH_AUTH_PASSWORD=123123
AGENDASH_AUTH_PASSWORD=123123 \
# Sign-up restriction
SIGNUP_DISABLED= \
SIGNUP_ALLOWED_DOMAINS= \
SIGNUP_ALLOWED_EMAILS=
ENV MAIL_HOST=$MAIL_HOST \
MAIL_USERNAME=$MAIL_USERNAME \
@@ -68,7 +72,11 @@ ENV MAIL_HOST=$MAIL_HOST \
# MongoDB
MONGODB_DATABASE_URL=$MONGODB_DATABASE_URL \
# Application
BASE_URL=$BASE_URL
BASE_URL=$BASE_URL \
# Sign-up restriction
SIGNUP_DISABLED=$SIGNUP_DISABLED \
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
# Create app directory.
WORKDIR /app

View File

@@ -3,7 +3,12 @@ import { check, param, query } from 'express-validator';
import { Service, Inject } from 'typedi';
import asyncMiddleware from '@/api/middleware/asyncMiddleware';
import BaseController from '@/api/controllers/BaseController';
import { AbilitySubject, AccountAction, IAccountDTO } from '@/interfaces';
import {
AbilitySubject,
AccountAction,
IAccountDTO,
IAccountsStructureType,
} from '@/interfaces';
import { ServiceError } from '@/exceptions';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { DATATYPES_LENGTH } from '@/data/DataTypes';
@@ -172,6 +177,11 @@ export default class AccountsController extends BaseController {
query('inactive_mode').optional().isBoolean().toBoolean(),
query('search_keyword').optional({ nullable: true }).isString().trim(),
query('structure')
.optional()
.isString()
.isIn([IAccountsStructureType.Tree, IAccountsStructureType.Flat]),
];
}
@@ -341,6 +351,7 @@ export default class AccountsController extends BaseController {
sortOrder: 'desc',
columnSortBy: 'created_at',
inactiveMode: false,
structure: IAccountsStructureType.Tree,
...this.matchedQueryData(req),
};

View File

@@ -49,6 +49,7 @@ export default class AuthenticationController extends BaseController {
asyncMiddleware(this.resetPassword.bind(this)),
this.handlerErrors
);
router.get('/meta', asyncMiddleware(this.getAuthMeta.bind(this)));
return router;
}
@@ -207,6 +208,23 @@ export default class AuthenticationController extends BaseController {
}
}
/**
* Retrieves the authentication meta for SPA.
* @param {Request} req
* @param {Response} res
* @param {Function} next
* @returns {Response|void}
*/
private async getAuthMeta(req: Request, res: Response, next: Function) {
try {
const meta = await this.authApplication.getAuthMeta();
return res.status(200).send({ meta });
} catch (error) {
next(error);
}
}
/**
* Handles the service errors.
*/
@@ -247,6 +265,30 @@ export default class AuthenticationController extends BaseController {
errors: [{ type: 'EMAIL.EXISTS', code: 600 }],
});
}
if (error.errorType === 'SIGNUP_RESTRICTED') {
return res.status(400).send({
errors: [
{
type: 'SIGNUP_RESTRICTED',
message:
'Sign-up is restricted no one can sign-up to the system.',
code: 700,
},
],
});
}
if (error.errorType === 'SIGNUP_RESTRICTED_NOT_ALLOWED') {
return res.status(400).send({
errors: [
{
type: 'SIGNUP_RESTRICTED_NOT_ALLOWED',
message:
'Sign-up is restricted the given email address is not allowed to sign-up.',
code: 710,
},
],
});
}
}
next(error);
}

View File

@@ -67,6 +67,7 @@ export default class GeneralLedgerReportController extends BaseFinancialReportCo
try {
const { data, query, meta } =
await this.generalLedgetService.generalLedger(tenantId, filter);
return res.status(200).send({
meta: this.transfromToResponse(meta),
data: this.transfromToResponse(data),

View File

@@ -4,6 +4,7 @@ import moment from 'moment';
global.__root_dir = path.join(__dirname, '..');
global.__resources_dir = path.join(global.__root_dir, 'resources');
global.__locales_dir = path.join(global.__resources_dir, 'locales');
global.__views_dir = path.join(global.__root_dir, 'views');
moment.prototype.toMySqlDateTime = function () {
return this.format('YYYY-MM-DD HH:mm:ss');

View File

@@ -1,5 +1,6 @@
import dotenv from 'dotenv';
import path from 'path';
import { castCommaListEnvVarToArray, parseBoolean } from '@/utils';
dotenv.config();
@@ -146,6 +147,19 @@ module.exports = {
},
},
/**
* Sign-up restrictions
*/
signupRestrictions: {
disabled: parseBoolean<boolean>(process.env.SIGNUP_DISABLED, false),
allowedDomains: castCommaListEnvVarToArray(
process.env.SIGNUP_ALLOWED_DOMAINS
),
allowedEmails: castCommaListEnvVarToArray(
process.env.SIGNUP_ALLOWED_EMAILS
),
},
/**
* Puppeteer remote browserless connection.
*/

View File

@@ -79,9 +79,15 @@ export interface IAccountTransaction {
}
export interface IAccountResponse extends IAccount {}
export enum IAccountsStructureType {
Tree = 'tree',
Flat = 'flat',
}
export interface IAccountsFilter extends IDynamicListFilterDTO {
stringifiedFilterRoles?: string;
onlyInactive: boolean;
structure?: IAccountsStructureType;
}
export interface IAccountType {

View File

@@ -74,4 +74,8 @@ export interface IAuthSendingResetPassword {
export interface IAuthSendedResetPassword {
user: ISystemUser,
token: string;
}
export interface IAuthGetMetaPOJO {
signupDisabled: boolean;
}

View File

@@ -1,6 +1,7 @@
import { AnyObject } from '@casl/ability/dist/types/types';
import { ITenant } from '@/interfaces';
import { Model } from 'objection';
import { Tenant } from '@/system/models';
export interface ISystemUser extends Model {
id: number;
@@ -54,20 +55,52 @@ export interface IUserInvite {
export interface IInviteUserService {
acceptInvite(token: string, inviteUserInput: IInviteUserInput): Promise<void>;
/**
* Re-send user invite.
* @param {number} tenantId -
* @param {string} email -
* @return {Promise<{ invite: IUserInvite }>}
*/
resendInvite(
tenantId: number,
userId: number,
authorizedUser: ISystemUser
): Promise<{
invite: IUserInvite;
user: ITenantUser;
}>;
/**
* Sends invite mail to the given email from the given tenant and user.
* @param {number} tenantId -
* @param {string} email -
* @param {IUser} authorizedUser -
* @return {Promise<IUserInvite>}
*/
sendInvite(
tenantId: number,
email: string,
sendInviteDTO: IUserSendInviteDTO,
authorizedUser: ISystemUser
): Promise<{
invite: IUserInvite;
invitedUser: ITenantUser;
}>;
}
export interface IAcceptInviteUserService {
/**
* Accept the received invite.
* @param {string} token
* @param {IInviteUserInput} inviteUserInput
* @throws {ServiceErrors}
* @returns {Promise<void>}
*/
acceptInvite(token: string, inviteUserDTO: IInviteUserInput): Promise<void>;
/**
* Validate the given invite token.
* @param {string} token - the given token string.
* @throws {ServiceError}
*/
checkInvite(
token: string
): Promise<{ inviteToken: IUserInvite; orgName: object }>;
@@ -121,7 +154,7 @@ export interface IUserInvitedEventPayload {
tenantId: number;
user: ITenantUser;
}
export interface IUserInviteTenantSyncedEventPayload{
export interface IUserInviteTenantSyncedEventPayload {
invite: IUserInvite;
authorizedUser: ISystemUser;
tenantId: number;
@@ -143,10 +176,10 @@ export interface IAcceptInviteEventPayload {
export interface ICheckInviteEventPayload {
inviteToken: IUserInvite;
tenant: ITenant
tenant: Tenant;
}
export interface IUserSendInviteDTO {
email: string;
roleId: number;
}
}

View File

@@ -1,5 +1,6 @@
import { Container, Inject } from 'typedi';
import InviteUserService from '@/services/InviteUsers/AcceptInviteUser';
import SendInviteUsersMailMessage from '@/services/InviteUsers/SendInviteUsersMailMessage';
export default class UserInviteMailJob {
/**
@@ -21,24 +22,17 @@ export default class UserInviteMailJob {
*/
public async handler(job, done: Function): Promise<void> {
const { invite, authorizedUser, tenantId } = job.attrs.data;
const Logger = Container.get('logger');
const inviteUsersService = Container.get(InviteUserService);
Logger.info(`Send invite user mail - started: ${job.attrs.data}`);
const sendInviteMailMessage = Container.get(SendInviteUsersMailMessage);
try {
await inviteUsersService.mailMessages.sendInviteMail(
await sendInviteMailMessage.sendInviteMail(
tenantId,
authorizedUser,
invite
);
Logger.info(`Send invite user mail - finished: ${job.attrs.data}`);
done();
} catch (error) {
Logger.info(
`Send invite user mail - error: ${job.attrs.data}, error: ${error}`
);
console.log(error);
done(error);
}
}

View File

@@ -109,7 +109,7 @@ export default class Mail {
* Retrieve view content from the view directory.
*/
private getViewContent(): string {
const filePath = path.join(global.__root_dir, `../views/${this.view}`);
const filePath = path.join(global.__views_dir, `/${this.view}`);
return fs.readFileSync(filePath, 'utf8');
}
}

View File

@@ -2,6 +2,7 @@ import moment from 'moment';
import * as R from 'ramda';
import { includes, isFunction, isObject, isUndefined, omit } from 'lodash';
import { formatNumber } from 'utils';
import { isArrayLikeObject } from 'lodash/fp';
export class Transformer {
public context: any;
@@ -39,12 +40,33 @@ export class Transformer {
return object;
};
/**
*
* @param object
* @returns
*/
protected preCollectionTransform = (object: any) => {
return object;
};
/**
*
* @param object
* @returns
*/
protected postCollectionTransform = (object: any) => {
return object;
};
/**
*
*/
public work = (object: any) => {
if (Array.isArray(object)) {
return object.map(this.getTransformation);
const preTransformed = this.preCollectionTransform(object);
const transformed = preTransformed.map(this.getTransformation);
return this.postCollectionTransform(transformed);
} else if (isObject(object)) {
return this.getTransformation(object);
}

View File

@@ -22,7 +22,7 @@ import SaleInvoiceAutoIncrementSubscriber from '@/subscribers/SaleInvoices/AutoI
import SaleInvoiceConvertFromEstimateSubscriber from '@/subscribers/SaleInvoices/ConvertFromEstimate';
import PaymentReceiveAutoSerialSubscriber from '@/subscribers/PaymentReceive/AutoSerialIncrement';
import SyncSystemSendInvite from '@/services/InviteUsers/SyncSystemSendInvite';
import InviteSendMainNotification from '@/services/InviteUsers/InviteSendMailNotification';
import InviteSendMainNotification from '@/services/InviteUsers/InviteSendMailNotificationSubscribe';
import SyncTenantAcceptInvite from '@/services/InviteUsers/SyncTenantAcceptInvite';
import SyncTenantUserMutate from '@/services/Users/SyncTenantUserSaved';
import { SyncTenantUserDelete } from '@/services/Users/SyncTenantUserDeleted';

View File

@@ -1,6 +1,11 @@
import { IAccount } from '@/interfaces';
import { IAccount, IAccountsStructureType } from '@/interfaces';
import { Transformer } from '@/lib/Transformer/Transformer';
import { formatNumber } from 'utils';
import {
assocDepthLevelToObjectTree,
flatToNestedArray,
formatNumber,
nestedArrayToFlatten,
} from 'utils';
export class AccountTransformer extends Transformer {
/**
@@ -8,7 +13,23 @@ export class AccountTransformer extends Transformer {
* @returns {Array}
*/
public includeAttributes = (): string[] => {
return ['formattedAmount'];
return ['formattedAmount', 'flattenName'];
};
/**
* Retrieves the flatten name with all dependants accounts names.
* @param {IAccount} account -
* @returns {string}
*/
public flattenName = (account: IAccount): string => {
const parentDependantsIds = this.options.accountsGraph.dependantsOf(
account.id
);
const prefixAccounts = parentDependantsIds.map((dependId) => {
const node = this.options.accountsGraph.getNodeData(dependId);
return `${node.name}: `;
});
return `${prefixAccounts}${account.name}`;
};
/**
@@ -17,8 +38,28 @@ export class AccountTransformer extends Transformer {
* @returns {string}
*/
protected formattedAmount = (account: IAccount): string => {
return formatNumber(account.amount, {
currencyCode: account.currencyCode,
return formatNumber(account.amount, { currencyCode: account.currencyCode });
};
/**
* Transformes the accounts collection to flat or nested array.
* @param {IAccount[]}
* @returns {IAccount[]}
*/
protected postCollectionTransform = (accounts: IAccount[]) => {
// Transfom the flatten to accounts tree.
const transformed = flatToNestedArray(accounts, {
id: 'id',
parentId: 'parentAccountId',
});
// Associate `accountLevel` attr to indicate object depth.
const transformed2 = assocDepthLevelToObjectTree(
transformed,
1,
'accountLevel'
);
return this.options.structure === IAccountsStructureType.Flat
? nestedArrayToFlatten(transformed2)
: transformed2;
};
}

View File

@@ -22,15 +22,19 @@ export class GetAccount {
*/
public getAccount = async (tenantId: number, accountId: number) => {
const { Account } = this.tenancy.models(tenantId);
const { accountRepository } = this.tenancy.repositories(tenantId);
// Find the given account or throw not found error.
const account = await Account.query().findById(accountId).throwIfNotFound();
const accountsGraph = await accountRepository.getDependencyGraph();
// Transformes the account model to POJO.
const transformed = await this.transformer.transform(
tenantId,
account,
new AccountTransformer()
new AccountTransformer(),
{ accountsGraph }
);
return this.i18nService.i18nApply(
[['accountTypeLabel'], ['accountNormalFormatted']],

View File

@@ -1,6 +1,11 @@
import { Inject, Service } from 'typedi';
import * as R from 'ramda';
import { IAccountsFilter, IAccountResponse, IFilterMeta } from '@/interfaces';
import {
IAccountsFilter,
IAccountResponse,
IFilterMeta,
IAccountsStructureType,
} from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import DynamicListingService from '@/services/DynamicListing/DynamicListService';
import { AccountTransformer } from './AccountTransform';
@@ -38,6 +43,7 @@ export class GetAccounts {
filterDTO: IAccountsFilter
): Promise<{ accounts: IAccountResponse[]; filterMeta: IFilterMeta }> => {
const { Account } = this.tenancy.models(tenantId);
const { accountRepository } = this.tenancy.repositories(tenantId);
// Parses the stringified filter roles.
const filter = this.parseListFilterDTO(filterDTO);
@@ -53,17 +59,16 @@ export class GetAccounts {
dynamicList.buildQuery()(builder);
builder.modify('inactiveMode', filter.inactiveMode);
});
// Retrievs the formatted accounts collection.
const preTransformedAccounts = await this.transformer.transform(
const accountsGraph = await accountRepository.getDependencyGraph();
// Retrieves the transformed accounts collection.
const transformedAccounts = await this.transformer.transform(
tenantId,
accounts,
new AccountTransformer()
new AccountTransformer(),
{ accountsGraph, structure: filterDTO.structure }
);
// Transform accounts to nested array.
const transformedAccounts = flatToNestedArray(preTransformedAccounts, {
id: 'id',
parentId: 'parentAccountId',
});
return {
accounts: transformedAccounts,

View File

@@ -1,8 +1,14 @@
import { Service, Inject, Container } from 'typedi';
import { IRegisterDTO, ISystemUser, IPasswordReset } from '@/interfaces';
import {
IRegisterDTO,
ISystemUser,
IPasswordReset,
IAuthGetMetaPOJO,
} from '@/interfaces';
import { AuthSigninService } from './AuthSignin';
import { AuthSignupService } from './AuthSignup';
import { AuthSendResetPassword } from './AuthSendResetPassword';
import { GetAuthMeta } from './GetAuthMeta';
@Service()
export default class AuthenticationApplication {
@@ -15,6 +21,9 @@ export default class AuthenticationApplication {
@Inject()
private authResetPasswordService: AuthSendResetPassword;
@Inject()
private authGetMeta: GetAuthMeta;
/**
* Signin and generates JWT token.
* @throws {ServiceError}
@@ -53,4 +62,12 @@ export default class AuthenticationApplication {
public async resetPassword(token: string, password: string): Promise<void> {
return this.authResetPasswordService.resetPassword(token, password);
}
/**
* Retrieves the authentication meta for SPA.
* @returns {Promise<IAuthGetMetaPOJO>}
*/
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
return this.authGetMeta.getAuthMeta();
}
}

View File

@@ -1,4 +1,4 @@
import { omit } from 'lodash';
import { isEmpty, omit } from 'lodash';
import moment from 'moment';
import { ServiceError } from '@/exceptions';
import {
@@ -13,6 +13,7 @@ import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import TenantsManagerService from '../Tenancy/TenantsManager';
import events from '@/subscribers/events';
import { hashPassword } from '@/utils';
import config from '@/config';
export class AuthSignupService {
@Inject()
@@ -33,6 +34,9 @@ export class AuthSignupService {
public async signUp(signupDTO: IRegisterDTO): Promise<ISystemUser> {
const { systemUserRepository } = this.sysRepositories;
// Validates the signup disable restrictions.
await this.validateSignupRestrictions(signupDTO.email);
// Validates the given email uniqiness.
await this.validateEmailUniqiness(signupDTO.email);
@@ -74,4 +78,34 @@ export class AuthSignupService {
throw new ServiceError(ERRORS.EMAIL_EXISTS);
}
}
/**
* Validate sign-up disable restrictions.
* @param {string} email
*/
private async validateSignupRestrictions(email: string) {
// Can't continue if the signup is not disabled.
if (!config.signupRestrictions.disabled) return;
// Validate the allowed email addresses and domains.
if (
!isEmpty(config.signupRestrictions.allowedEmails) ||
!isEmpty(config.signupRestrictions.allowedDomains)
) {
const emailDomain = email.split('@').pop();
const isAllowedEmail =
config.signupRestrictions.allowedEmails.indexOf(email) !== -1;
const isAllowedDomain = config.signupRestrictions.allowedDomains.some(
(domain) => emailDomain === domain
);
if (!isAllowedEmail && !isAllowedDomain) {
throw new ServiceError(ERRORS.SIGNUP_RESTRICTED_NOT_ALLOWED);
}
// Throw error if the signup is disabled with no exceptions.
} else {
throw new ServiceError(ERRORS.SIGNUP_RESTRICTED);
}
}
}

View File

@@ -0,0 +1,16 @@
import { Service } from 'typedi';
import { IAuthGetMetaPOJO } from '@/interfaces';
import config from '@/config';
@Service()
export class GetAuthMeta {
/**
* Retrieves the authentication meta for SPA.
* @returns {Promise<IAuthGetMetaPOJO>}
*/
public async getAuthMeta(): Promise<IAuthGetMetaPOJO> {
return {
signupDisabled: config.signupRestrictions.disabled,
};
}
}

View File

@@ -7,4 +7,6 @@ export const ERRORS = {
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
PHONE_NUMBER_EXISTS: 'PHONE_NUMBER_EXISTS',
EMAIL_EXISTS: 'EMAIL_EXISTS',
SIGNUP_RESTRICTED_NOT_ALLOWED: 'SIGNUP_RESTRICTED_NOT_ALLOWED',
SIGNUP_RESTRICTED: 'SIGNUP_RESTRICTED',
};

View File

@@ -5,7 +5,7 @@ import { ICashflowAccountTransactionsQuery, IPaginationMeta } from '@/interfaces
@Service()
export default class CashflowAccountTransactionsRepo {
@Inject()
tenancy: HasTenancyService;
private tenancy: HasTenancyService;
/**
* Retrieve the cashflow account transactions.

View File

@@ -12,9 +12,12 @@ import {
} from '@/interfaces';
import { ERRORS } from './constants';
import { EventPublisher } from '@/lib/EventPublisher/EventPublisher';
import { IAcceptInviteUserService } from '@/interfaces';
@Service()
export default class AcceptInviteUserService {
export default class AcceptInviteUserService
implements IAcceptInviteUserService
{
@Inject()
private eventPublisher: EventPublisher;

View File

@@ -1,7 +1,4 @@
import {
IUserInvitedEventPayload,
IUserInviteTenantSyncedEventPayload,
} from '@/interfaces';
import { IUserInviteTenantSyncedEventPayload } from '@/interfaces';
import events from '@/subscribers/events';
import { Inject, Service } from 'typedi';

View File

@@ -1,12 +1,12 @@
import path from 'path';
import { ISystemUser } from '@/interfaces';
import TenancyService from '@/services/Tenancy/TenancyService';
import Mail from '@/lib/Mail';
import { Service, Container } from 'typedi';
import config from '@/config';
import { Service } from 'typedi';
import { Tenant } from '@/system/models';
import config from '@/config';
@Service()
export default class InviteUsersMailMessages {
export default class SendInviteUsersMailMessage {
/**
* Sends invite mail to the given email.
* @param user
@@ -18,7 +18,7 @@ export default class InviteUsersMailMessages {
.findById(tenantId)
.withGraphFetched('metadata');
const root = __dirname + '/../../../views/images/bigcapital.png';
const root = path.join(global.__views_dir, '/images/bigcapital.png');
const mail = new Mail()
.setSubject(`${fromUser.firstName} has invited you to join a Bigcapital`)

View File

@@ -8,7 +8,7 @@ import { IAcceptInviteEventPayload } from '@/interfaces';
@Service()
export default class SyncTenantAcceptInvite {
@Inject()
tenancy: HasTenancyService;
private tenancy: HasTenancyService;
/**
* Attaches events with handlers.

View File

@@ -74,17 +74,15 @@ export default class InviteTenantUserService implements IInviteUserService {
/**
* Re-send user invite.
* @param {number} tenantId -
* @param {string} email -
* @param {number} tenantId -
* @param {string} email -
* @return {Promise<{ invite: IUserInvite }>}
*/
public async resendInvite(
tenantId: number,
userId: number,
authorizedUser: ISystemUser
): Promise<{
user: ITenantUser;
}> {
): Promise<{ user: ITenantUser }> {
// Retrieve the user by id or throw not found service error.
const user = await this.getUserByIdOrThrowError(tenantId, userId);

View File

@@ -419,6 +419,58 @@ export const parseDate = (date: string) => {
return date ? moment(date).utcOffset(0).format('YYYY-MM-DD') : '';
};
const nestedArrayToFlatten = (
collection,
property = 'children',
parseItem = (a, level) => a,
level = 1
) => {
const parseObject = (obj) =>
parseItem(
{
..._.omit(obj, [property]),
},
level
);
return collection.reduce((items, currentValue, index) => {
let localItems = [...items];
const parsedItem = parseObject(currentValue, level);
localItems.push(parsedItem);
if (Array.isArray(currentValue[property])) {
const flattenArray = nestedArrayToFlatten(
currentValue[property],
property,
parseItem,
level + 1
);
localItems = _.concat(localItems, flattenArray);
}
return localItems;
}, []);
};
const assocDepthLevelToObjectTree = (
objects,
level = 1,
propertyName = 'level'
) => {
for (let i = 0; i < objects.length; i++) {
const object = objects[i];
object[propertyName] = level;
if (object.children) {
assocDepthLevelToObjectTree(object.children, level + 1, propertyName);
}
}
return objects;
};
const castCommaListEnvVarToArray = (envVar: string): Array<string> => {
return envVar ? envVar?.split(',')?.map(_.trim) : [];
};
export {
templateRender,
accumSum,
@@ -449,4 +501,7 @@ export {
dateRangeFromToCollection,
transformToMapKeyValue,
mergeObjectsBykey,
nestedArrayToFlatten,
assocDepthLevelToObjectTree,
castCommaListEnvVarToArray
};

View File

@@ -1227,9 +1227,9 @@
}
},
"@blueprintjs-formik/select": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.1.5.tgz",
"integrity": "sha512-EqGbuoiS1VrWpzjd39uVhBAmfVobdpgqalGcpODyGA+XAYoft1UU12yzTzrEOwBZpQKiC12UQwekUPspYBsVKA==",
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@blueprintjs-formik/select/-/select-0.2.3.tgz",
"integrity": "sha512-j/zkX0B9wgtoHgK6Z/rlowB7F7zemrAajBU+d3caCoEYMMqwAI0XA++GytqrIhv5fEGjkZ1hkxS9j8eqX8vtjA==",
"requires": {
"lodash.get": "^4.4.2",
"lodash.keyby": "^4.6.0",

View File

@@ -5,7 +5,7 @@
"dependencies": {
"@blueprintjs-formik/core": "^0.2.1",
"@blueprintjs-formik/datetime": "^0.3.4",
"@blueprintjs-formik/select": "^0.1.4",
"@blueprintjs-formik/select": "^0.2.3",
"@blueprintjs/core": "^3.50.2",
"@blueprintjs/datetime": "^3.23.12",
"@blueprintjs/popover2": "^0.11.1",

View File

@@ -1,74 +0,0 @@
// @ts-nocheck
import React from 'react';
import styled from 'styled-components';
import { MenuItem } from '@blueprintjs/core';
import { FMultiSelect } from '../Forms';
import classNames from 'classnames';
import { Classes } from '@blueprintjs/popover2';
/**
*
* @param {*} query
* @param {*} account
* @param {*} _index
* @param {*} exactMatch
* @returns
*/
const accountItemPredicate = (query, account, _index, exactMatch) => {
const normalizedTitle = account.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return `${account.code}. ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
}
};
/**
*
* @param {*} account
* @param {*} param1
* @returns
*/
const accountItemRenderer = (
account,
{ handleClick, modifiers, query },
{ isSelected },
) => {
return (
<MenuItem
icon={isSelected ? 'tick' : 'blank'}
text={account.name}
label={account.code}
key={account.id}
onClick={handleClick}
/>
);
};
const accountSelectProps = {
itemPredicate: accountItemPredicate,
itemRenderer: accountItemRenderer,
valueAccessor: (item) => item.id,
labelAccessor: (item) => item.code,
tagRenderer: (item) => item.name,
};
/**
* branches mulit select.
* @param {*} param0
* @returns {JSX.Element}
*/
export function AccountMultiSelect({ accounts, ...rest }) {
return (
<FMultiSelect
items={accounts}
popoverProps={{
minimal: true,
}}
{...accountSelectProps}
{...rest}
/>
);
}

View File

@@ -1,31 +1,97 @@
// @ts-nocheck
import React from 'react';
import { MenuItem } from '@blueprintjs/core';
import { MultiSelect } from '../MultiSelectTaggable';
import { FMultiSelect } from '../Forms';
import { accountPredicate } from './_components';
import { MenuItemNestedText } from '../Menu';
import { usePreprocessingAccounts } from './_hooks';
export function AccountsMultiSelect({ ...multiSelectProps }) {
// Create new account renderer.
const createNewItemRenderer = (query, active, handleClick) => {
return (
<MultiSelect
itemRenderer={(
item,
{ active, selected, handleClick, modifiers, query },
) => {
return (
<MenuItem
active={active}
icon={selected ? 'tick' : 'blank'}
text={item.name}
label={item.code}
key={item.id}
onClick={handleClick}
/>
);
}}
<MenuItem
icon="add"
text={intl.get('list.create', { value: `"${query}"` })}
active={active}
onClick={handleClick}
/>
);
};
/**
* Default account item renderer of the list.
* @returns {JSX.Element}
*/
const accountRenderer = (
item,
{ handleClick, modifiers, query },
{ isSelected },
) => {
if (!modifiers.matchesPredicate) {
return null;
}
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
text={<MenuItemNestedText level={item.account_level} text={item.name} />}
key={item.id}
onClick={handleClick}
icon={isSelected ? 'tick' : 'blank'}
/>
);
};
// Create new item from the given query string.
const createNewItemFromQuery = (name) => ({ name });
/**
* Accounts multi-select field binded with Formik form.
* @returns {JSX.Element}
*/
export function AccountsMultiSelect({
items,
allowCreate,
filterByRootTypes,
filterByParentTypes,
filterByTypes,
filterByNormal,
...rest
}) {
// Filters accounts based on filter props.
const filteredAccounts = usePreprocessingAccounts(items, {
filterByParentTypes,
filterByTypes,
filterByNormal,
filterByRootTypes,
});
// Maybe inject new item props to select component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
// Handles the create item click.
const handleCreateItemClick = () => {
openDialog(DialogsName.AccountForm);
};
return (
<FMultiSelect
items={filteredAccounts}
valueAccessor={'id'}
textAccessor={'name'}
labelAccessor={'code'}
tagAccessor={'name'}
popoverProps={{ minimal: true }}
fill={true}
tagRenderer={(item) => item.name}
resetOnSelect={true}
{...multiSelectProps}
itemPredicate={accountPredicate}
itemRenderer={accountRenderer}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
onCreateItemSelect={handleCreateItemClick}
{...rest}
/>
);
}

View File

@@ -0,0 +1,101 @@
// @ts-nocheck
import React from 'react';
import * as R from 'ramda';
import intl from 'react-intl-universal';
import { MenuItem } from '@blueprintjs/core';
import { MenuItemNestedText, FSelect } from '@/components';
import { accountPredicate } from './_components';
import { DialogsName } from '@/constants/dialogs';
import withDialogActions from '@/containers/Dialog/withDialogActions';
import { usePreprocessingAccounts } from './_hooks';
// Create new account renderer.
const createNewItemRenderer = (query, active, handleClick) => {
return (
<MenuItem
icon="add"
text={intl.get('list.create', { value: `"${query}"` })}
active={active}
onClick={handleClick}
/>
);
};
// Create new item from the given query string.
const createNewItemFromQuery = (name) => ({ name });
/**
* Default account item renderer.
* @returns {JSX.Element}
*/
const accountRenderer = (item, { handleClick, modifiers, query }) => {
if (!modifiers.matchesPredicate) {
return null;
}
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
label={item.code}
key={item.id}
text={<MenuItemNestedText level={item.account_level} text={item.name} />}
onClick={handleClick}
/>
);
};
/**
* Accounts select field binded with Formik form.
* @returns {JSX.Element}
*/
function AccountsSelectRoot({
// #withDialogActions
openDialog,
// #ownProps
items,
allowCreate,
filterByParentTypes,
filterByTypes,
filterByNormal,
filterByRootTypes,
...restProps
}) {
// Filters accounts based on filter props.
const filteredAccounts = usePreprocessingAccounts(items, {
filterByParentTypes,
filterByTypes,
filterByNormal,
filterByRootTypes,
});
// Maybe inject new item props to select component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
// Handles the create item click.
const handleCreateItemClick = () => {
openDialog(DialogsName.AccountForm);
};
return (
<FSelect
items={filteredAccounts}
textAccessor={'name'}
labelAccessor={'code'}
valueAccessor={'id'}
popoverProps={{ minimal: true, usePortal: true, inline: false }}
itemPredicate={accountPredicate}
itemRenderer={accountRenderer}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
onCreateItemSelect={handleCreateItemClick}
{...restProps}
/>
);
}
export const AccountsSelect = R.compose(withDialogActions)(AccountsSelectRoot);

View File

@@ -1,177 +0,0 @@
// @ts-nocheck
import React, { useCallback, useState, useEffect, useMemo } from 'react';
import intl from 'react-intl-universal';
import classNames from 'classnames';
import { MenuItem, Button } from '@blueprintjs/core';
import { Select } from '@blueprintjs/select';
import * as R from 'ramda';
import { MenuItemNestedText, FormattedMessage as T } from '@/components';
import { nestedArrayToflatten, filterAccountsByQuery } from '@/utils';
import { CLASSES } from '@/constants/classes';
import { DialogsName } from '@/constants/dialogs';
import withDialogActions from '@/containers/Dialog/withDialogActions';
// Create new account renderer.
const createNewItemRenderer = (query, active, handleClick) => {
return (
<MenuItem
icon="add"
text={intl.get('list.create', { value: `"${query}"` })}
active={active}
onClick={handleClick}
/>
);
};
// Create new item from the given query string.
const createNewItemFromQuery = (name) => {
return {
name,
};
};
// Filters accounts items.
const filterAccountsPredicater = (query, account, _index, exactMatch) => {
const normalizedTitle = account.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
}
};
/**
* Accounts select list.
*/
function AccountsSelectListRoot({
// #withDialogActions
openDialog,
// #ownProps
accounts,
initialAccountId,
selectedAccountId,
defaultSelectText = 'Select account',
onAccountSelected,
disabled = false,
popoverFill = false,
filterByParentTypes,
filterByTypes,
filterByNormal,
filterByRootTypes,
allowCreate,
buttonProps = {},
}) {
const flattenAccounts = useMemo(
() => nestedArrayToflatten(accounts),
[accounts],
);
// Filters accounts based on filter props.
const filteredAccounts = useMemo(() => {
let filteredAccounts = filterAccountsByQuery(flattenAccounts, {
filterByRootTypes,
filterByParentTypes,
filterByTypes,
filterByNormal,
});
return filteredAccounts;
}, [
flattenAccounts,
filterByRootTypes,
filterByParentTypes,
filterByTypes,
filterByNormal,
]);
// Find initial account object to set it as default account in initial render.
const initialAccount = useMemo(
() => filteredAccounts.find((a) => a.id === initialAccountId),
[initialAccountId, filteredAccounts],
);
// Select account item.
const [selectedAccount, setSelectedAccount] = useState(
initialAccount || null,
);
useEffect(() => {
if (typeof selectedAccountId !== 'undefined') {
const account = selectedAccountId
? filteredAccounts.find((a) => a.id === selectedAccountId)
: null;
setSelectedAccount(account);
}
}, [selectedAccountId, filteredAccounts, setSelectedAccount]);
// Account item of select accounts field.
const accountItem = useCallback((item, { handleClick, modifiers, query }) => {
return (
<MenuItem
text={<MenuItemNestedText level={item.level} text={item.name} />}
label={item.code}
key={item.id}
onClick={handleClick}
/>
);
}, []);
// Handle the account item select.
const handleAccountSelect = useCallback(
(account) => {
if (account.id) {
setSelectedAccount({ ...account });
onAccountSelected && onAccountSelected(account);
} else {
openDialog(DialogsName.AccountForm);
}
},
[setSelectedAccount, onAccountSelected, openDialog],
);
// Maybe inject new item props to select component.
const maybeCreateNewItemRenderer = allowCreate ? createNewItemRenderer : null;
const maybeCreateNewItemFromQuery = allowCreate
? createNewItemFromQuery
: null;
return (
<Select
items={filteredAccounts}
noResults={<MenuItem disabled={true} text={<T id={'no_accounts'} />} />}
itemRenderer={accountItem}
itemPredicate={filterAccountsPredicater}
popoverProps={{
minimal: true,
usePortal: !popoverFill,
inline: popoverFill,
}}
filterable={true}
onItemSelect={handleAccountSelect}
disabled={disabled}
className={classNames('form-group--select-list', {
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
})}
createNewItemRenderer={maybeCreateNewItemRenderer}
createNewItemFromQuery={maybeCreateNewItemFromQuery}
>
<Button
disabled={disabled}
text={selectedAccount ? selectedAccount.name : defaultSelectText}
{...buttonProps}
/>
</Select>
);
}
export const AccountsSelectList = R.compose(withDialogActions)(
AccountsSelectListRoot,
);

View File

@@ -1,49 +1,15 @@
// @ts-nocheck
import React, { useCallback } from 'react';
import classNames from 'classnames';
import { ListSelect } from '@/components';
import { CLASSES } from '@/constants/classes';
export function AccountsTypesSelect({
accountsTypes,
selectedTypeId,
defaultSelectText = 'Select account type',
onTypeSelected,
disabled = false,
popoverFill = false,
...restProps
}) {
// Filters accounts types items.
const filterAccountTypeItems = (query, accountType, _index, exactMatch) => {
const normalizedTitle = accountType.label.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return normalizedTitle.indexOf(normalizedQuery) >= 0;
}
};
// Handle item selected.
const handleItemSelected = (accountType) => {
onTypeSelected && onTypeSelected(accountType);
};
import React from 'react';
import { FSelect } from '@/components/Forms';
export function AccountsTypesSelect({ ...props }) {
return (
<ListSelect
items={accountsTypes}
selectedItemProp={'key'}
selectedItem={selectedTypeId}
textProp={'label'}
defaultText={defaultSelectText}
onItemSelect={handleItemSelected}
itemPredicate={filterAccountTypeItems}
disabled={disabled}
className={classNames('form-group--select-list', {
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
})}
{...restProps}
<FSelect
valueAccessor={'key'}
labelAccessor={'label'}
textAccessor={'label'}
placeholder={'Select an account...'}
{...props}
/>
);
}

View File

@@ -0,0 +1,14 @@
// @ts-nocheck
import React from 'react';
// Filters accounts items.
export const accountPredicate = (query, account, _index, exactMatch) => {
const normalizedTitle = account.name.toLowerCase();
const normalizedQuery = query.toLowerCase();
if (exactMatch) {
return normalizedTitle === normalizedQuery;
} else {
return `${account.code} ${normalizedTitle}`.indexOf(normalizedQuery) >= 0;
}
};

View File

@@ -0,0 +1,36 @@
import { useMemo } from 'react';
import { filterAccountsByQuery, nestedArrayToflatten } from '@/utils';
interface PreprocessingAccountsOptions {
filterByRootTypes: string[];
filterByParentTypes: string[];
filterByTypes: string[];
filterByNormal: string[];
}
export const usePreprocessingAccounts = (
items: any,
{
filterByRootTypes,
filterByParentTypes,
filterByTypes,
filterByNormal,
}: PreprocessingAccountsOptions,
) => {
return useMemo(() => {
const flattenAccounts = nestedArrayToflatten(items);
const filteredAccounts = filterAccountsByQuery(flattenAccounts, {
filterByRootTypes,
filterByParentTypes,
filterByTypes,
filterByNormal,
});
return filteredAccounts;
}, [
items,
filterByRootTypes,
filterByParentTypes,
filterByTypes,
filterByNormal,
]);
};

View File

@@ -1,5 +1,4 @@
export * from './AccountMultiSelect';
export * from './AccountsSelect';
export * from './AccountsMultiSelect';
export * from './AccountsSelectList';
export * from './AccountsSuggestField';
export * from './AccountsTypesSelect';

View File

@@ -77,11 +77,13 @@ export function BankAccount({
</BankAccountHeader>
<BankAccountMeta>
<BankAccountMetaLine
title={intl.get('cash_flow.label_account_transcations')}
value={2}
className={clsx({ [Classes.SKELETON]: loading })}
/>
{false && (
<BankAccountMetaLine
title={intl.get('cash_flow.transactions_for_review')}
value={'0'}
className={clsx({ [Classes.SKELETON]: loading })}
/>
)}
<BankAccountMetaLine
title={updatedBeforeText}
className={clsx({ [Classes.SKELETON]: loading })}

View File

@@ -2,7 +2,6 @@
import React from 'react';
import intl from 'react-intl-universal';
import { MenuItem, Button } from '@blueprintjs/core';
import { FSelect } from '../Forms';
/**
@@ -28,30 +27,6 @@ const currencyItemPredicate = (query, currency, _index, exactMatch) => {
}
};
/**
* @param {*} currency
* @returns
*/
const currencyItemRenderer = (currency, { handleClick, modifiers, query }) => {
return (
<MenuItem
active={modifiers.active}
disabled={modifiers.disabled}
text={currency.currency_name}
label={currency.currency_code.toString()}
key={currency.id}
onClick={handleClick}
/>
);
};
const currencySelectProps = {
itemPredicate: currencyItemPredicate,
itemRenderer: currencyItemRenderer,
valueAccessor: 'currency_code',
labelAccessor: 'currency_code',
};
/**
*
* @param {*} currencies
@@ -60,18 +35,13 @@ const currencySelectProps = {
export function CurrencySelect({ currencies, ...rest }) {
return (
<FSelect
{...currencySelectProps}
itemPredicate={currencyItemPredicate}
valueAccessor={'currency_code'}
textAccessor={'currency_name'}
labelAccessor={'currency_code'}
{...rest}
items={currencies}
input={CurrnecySelectButton}
placeholder={intl.get('select_currency_code')}
/>
);
}
/**
* @param {*} label
* @returns
*/
function CurrnecySelectButton({ label }) {
return <Button text={label ? label : intl.get('select_currency_code')} />;
}

View File

@@ -1,4 +1,5 @@
// @ts-nocheck
import React from 'react';
import {
FormGroup,
InputGroup,
@@ -9,8 +10,9 @@ import {
EditableText,
TextArea,
} from '@blueprintjs-formik/core';
import { Select, MultiSelect } from '@blueprintjs-formik/select';
import { MultiSelect } from '@blueprintjs-formik/select';
import { DateInput } from '@blueprintjs-formik/datetime';
import { FSelect } from './Select';
export {
FormGroup as FFormGroup,
@@ -19,7 +21,7 @@ export {
Checkbox as FCheckbox,
RadioGroup as FRadioGroup,
Switch as FSwitch,
Select as FSelect,
FSelect,
MultiSelect as FMultiSelect,
EditableText as FEditableText,
TextArea as FTextArea,

View File

@@ -0,0 +1,58 @@
// @ts-nocheck
import React from 'react';
import { Button } from '@blueprintjs/core';
import { Select } from '@blueprintjs-formik/select';
import styled from 'styled-components';
import clsx from 'classnames';
export function FSelect({ ...props }) {
const input = ({ activeItem, text, label, value }) => {
return (
<SelectButton
text={text || props.placeholder || 'Select an item ...'}
disabled={props.disabled || false}
{...props.buttonProps}
className={clsx({ 'is-selected': !!text }, props.className)}
/>
);
};
return <Select input={input} {...props} fill={true} />;
}
const SelectButton = styled(Button)`
outline: none;
box-shadow: 0 0 0 transparent;
border: 1px solid #ced4da;
position: relative;
padding-right: 30px;
&:not(.is-selected):not([class*='bp3-intent-']):not(.bp3-minimal) {
color: #5c7080;
}
&:after {
content: '';
display: inline-block;
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 5px solid #8d8d8d;
position: absolute;
right: 0;
top: 50%;
margin-top: -2px;
margin-right: 12px;
border-radius: 1px;
}
&:not([class*='bp3-intent-']) {
&,
&:hover {
background: #fff;
}
}
.bp3-intent-danger & {
border-color: #db3737;
}
`;

View File

@@ -0,0 +1,36 @@
// @ts-nocheck
import React, { createContext } from 'react';
import { useAuthMetadata } from '@/hooks/query';
import { Spinner } from '@blueprintjs/core';
import styled from 'styled-components';
const AuthMetaBootContext = createContext();
/**
* Boots the authentication page metadata.
*/
function AuthMetaBootProvider({ ...props }) {
const { isLoading: isAuthMetaLoading, data: authMeta } = useAuthMetadata();
const state = {
isAuthMetaLoading,
signupDisabled: authMeta?.meta?.signup_disabled,
};
if (isAuthMetaLoading) {
return (
<SpinnerRoot>
<Spinner size={30} value={null} />
</SpinnerRoot>
);
}
return <AuthMetaBootContext.Provider value={state} {...props} />;
}
const useAuthMetaBoot = () => React.useContext(AuthMetaBootContext);
export { AuthMetaBootContext, AuthMetaBootProvider, useAuthMetaBoot };
const SpinnerRoot = styled.div`
margin-top: 5rem;
`;

View File

@@ -10,12 +10,11 @@ import { Icon, FormattedMessage as T } from '@/components';
import { useIsAuthenticated } from '@/hooks/state';
import '@/style/pages/Authentication/Auth.scss';
import { AuthMetaBootProvider } from './AuthMetaBoot';
export function Authentication() {
const to = { pathname: '/' };
const location = useLocation();
const isAuthenticated = useIsAuthenticated();
const locationKey = location.pathname;
if (isAuthenticated) {
return <Redirect to={to} />;
@@ -28,30 +27,41 @@ export function Authentication() {
<Icon icon="bigcapital" height={37} width={214} />
</AuthLogo>
<TransitionGroup>
<CSSTransition
timeout={500}
key={locationKey}
classNames="authTransition"
>
<Switch>
{authenticationRoutes.map((route, index) => (
<Route
key={index}
path={route.path}
exact={route.exact}
component={route.component}
/>
))}
</Switch>
</CSSTransition>
</TransitionGroup>
<AuthMetaBootProvider>
<AuthenticationRoutes />
</AuthMetaBootProvider>
</AuthInsider>
</AuthPage>
</BodyClassName>
);
}
function AuthenticationRoutes() {
const location = useLocation();
const locationKey = location.pathname;
return (
<TransitionGroup>
<CSSTransition
timeout={500}
key={locationKey}
classNames="authTransition"
>
<Switch>
{authenticationRoutes.map((route, index) => (
<Route
key={index}
path={route.path}
exact={route.exact}
component={route.component}
/>
))}
</Switch>
</CSSTransition>
</TransitionGroup>
);
}
const AuthPage = styled.div``;
const AuthInsider = styled.div`
width: 384px;

View File

@@ -14,11 +14,12 @@ import {
AuthFooterLink,
AuthInsiderCard,
} from './_components';
import { useAuthMetaBoot } from './AuthMetaBoot';
const initialValues = {
crediential: '',
password: '',
keepLoggedIn: false
keepLoggedIn: false,
};
/**
@@ -64,12 +65,15 @@ export default function Login() {
}
function LoginFooterLinks() {
const { signupDisabled } = useAuthMetaBoot();
return (
<AuthFooterLinks>
<AuthFooterLink>
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
</AuthFooterLink>
{!signupDisabled && (
<AuthFooterLink>
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
</AuthFooterLink>
)}
<AuthFooterLink>
<Link to={'/auth/send_reset_password'}>
<T id={'forget_my_password'} />

View File

@@ -10,7 +10,7 @@ import AuthInsider from '@/containers/Authentication/AuthInsider';
import { useAuthLogin, useAuthRegister } from '@/hooks/query/authentication';
import RegisterForm from './RegisterForm';
import { RegisterSchema, transformRegisterErrorsToForm } from './utils';
import { RegisterSchema, transformRegisterErrorsToForm, transformRegisterToastMessages } from './utils';
import {
AuthFooterLinks,
AuthFooterLink,
@@ -57,7 +57,11 @@ export default function RegisterUserForm() {
},
}) => {
const formErrors = transformRegisterErrorsToForm(errors);
const toastMessages = transformRegisterToastMessages(errors);
toastMessages.forEach((toastMessage) => {
AppToaster.show(toastMessage);
});
setErrors(formErrors);
setSubmitting(false);
},

View File

@@ -16,6 +16,7 @@ import {
} from './_components';
import ResetPasswordForm from './ResetPasswordForm';
import { ResetPasswordSchema } from './utils';
import { useAuthMetaBoot } from './AuthMetaBoot';
const initialValues = {
password: '',
@@ -79,12 +80,15 @@ export default function ResetPassword() {
}
function ResetPasswordFooterLinks() {
const { signupDisabled } = useAuthMetaBoot();
return (
<AuthFooterLinks>
<AuthFooterLink>
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
</AuthFooterLink>
{!signupDisabled && (
<AuthFooterLink>
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
</AuthFooterLink>
)}
<AuthFooterLink>
Return to <Link to={'/auth/login'}>Sign In</Link>
</AuthFooterLink>

View File

@@ -19,6 +19,7 @@ import {
transformSendResetPassErrorsToToasts,
} from './utils';
import AuthInsider from '@/containers/Authentication/AuthInsider';
import { useAuthMetaBoot } from './AuthMetaBoot';
const initialValues = {
crediential: '',
@@ -27,7 +28,7 @@ const initialValues = {
/**
* Send reset password page.
*/
export default function SendResetPassword({ requestSendResetPassword }) {
export default function SendResetPassword() {
const history = useHistory();
const { mutateAsync: sendResetPasswordMutate } = useAuthSendResetPassword();
@@ -75,12 +76,15 @@ export default function SendResetPassword({ requestSendResetPassword }) {
}
function SendResetPasswordFooterLinks() {
const { signupDisabled } = useAuthMetaBoot();
return (
<AuthFooterLinks>
<AuthFooterLink>
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
</AuthFooterLink>
{!signupDisabled && (
<AuthFooterLink>
Don't have an account? <Link to={'/auth/register'}>Sign up</Link>
</AuthFooterLink>
)}
<AuthFooterLink>
Return to <Link to={'/auth/login'}>Sign In</Link>
</AuthFooterLink>

View File

@@ -94,3 +94,21 @@ export const transformRegisterErrorsToForm = (errors) => {
}
return formErrors;
};
export const transformRegisterToastMessages = (errors) => {
const toastErrors = [];
if (errors.some((e) => e.type === 'SIGNUP_RESTRICTED_NOT_ALLOWED')) {
toastErrors.push({
message:
'The sign-up is restricted, the given email address is not allowed to sign-up.',
intent: Intent.DANGER,
});
} else if (errors.find((e) => e.type === 'SIGNUP_RESTRICTED')) {
toastErrors.push({
message: 'Sign-up is disabled, and no new accounts can be created.',
intent: Intent.DANGER,
});
}
return toastErrors;
};

View File

@@ -1,32 +1,29 @@
// @ts-nocheck
import React from 'react';
import classNames from 'classnames';
import { Form, FastField, Field, ErrorMessage, useFormikContext } from 'formik';
import {
Button,
Classes,
FormGroup,
InputGroup,
Intent,
TextArea,
Checkbox,
} from '@blueprintjs/core';
import { Form, useFormikContext } from 'formik';
import { Button, Classes, FormGroup, Intent } from '@blueprintjs/core';
import {
If,
FieldRequiredHint,
Hint,
AccountsSelectList,
AccountsSelect,
AccountsTypesSelect,
CurrencySelect,
FormattedMessage as T,
FFormGroup,
FInputGroup,
FCheckbox,
FTextArea,
} from '@/components';
import withAccounts from '@/containers/Accounts/withAccounts';
import { inputIntent, compose } from '@/utils';
import { useAutofocus } from '@/hooks';
import { FOREIGN_CURRENCY_ACCOUNTS } from '@/constants/accountTypes';
import { useAutofocus } from '@/hooks';
import { useAccountDialogContext } from './AccountDialogProvider';
import { parentAccountShouldUpdate } from './utils';
import { compose } from '@/utils';
/**
* Account form dialogs fields.
@@ -36,7 +33,7 @@ function AccountFormDialogFields({
onClose,
action,
}) {
const { values, isSubmitting } = useFormikContext();
const { values, isSubmitting, setFieldValue } = useFormikContext();
const accountNameFieldRef = useAutofocus();
// Account form context.
@@ -46,146 +43,120 @@ function AccountFormDialogFields({
return (
<Form>
<div className={Classes.DIALOG_BODY}>
<Field name={'account_type'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'account_type'} />}
labelInfo={<FieldRequiredHint />}
className={classNames('form-group--account-type', Classes.FILL)}
inline={true}
helperText={<ErrorMessage name="account_type" />}
intent={inputIntent({ error, touched })}
>
<AccountsTypesSelect
accountsTypes={accountsTypes}
selectedTypeId={value}
defaultSelectText={<T id={'select_account_type'} />}
onTypeSelected={(accountType) => {
form.setFieldValue('account_type', accountType.key);
form.setFieldValue('currency_code', '');
}}
disabled={fieldsDisabled.accountType}
popoverProps={{ minimal: true }}
popoverFill={true}
/>
</FormGroup>
)}
</Field>
<FastField name={'name'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'account_name'} />}
labelInfo={<FieldRequiredHint />}
className={'form-group--account-name'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="name" />}
inline={true}
>
<InputGroup
medium={true}
inputRef={(ref) => (accountNameFieldRef.current = ref)}
{...field}
/>
</FormGroup>
)}
</FastField>
<FastField name={'code'}>
{({ form, field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'account_code'} />}
className={'form-group--account-code'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="code" />}
inline={true}
labelInfo={<Hint content={<T id="account_code_hint" />} />}
>
<InputGroup medium={true} {...field} />
</FormGroup>
)}
</FastField>
<Field name={'subaccount'} type={'checkbox'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={' '}
className={classNames('form-group--subaccount')}
intent={inputIntent({ error, touched })}
inline={true}
>
<Checkbox
inline={true}
label={<T id={'sub_account'} />}
name={'subaccount'}
{...field}
/>
</FormGroup>
)}
</Field>
<FastField
name={'parent_account_id'}
shouldUpdate={parentAccountShouldUpdate}
<FFormGroup
inline={true}
label={<T id={'account_type'} />}
labelInfo={<FieldRequiredHint />}
name={'account_type'}
fastField={true}
>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={<T id={'parent_account'} />}
className={classNames('form-group--parent-account', Classes.FILL)}
inline={true}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="parent_account_id" />}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={(account) => {
setFieldValue('parent_account_id', account.id);
}}
defaultSelectText={<T id={'select_parent_account'} />}
selectedAccountId={value}
popoverFill={true}
filterByTypes={values.account_type}
disabled={!values.subaccount}
/>
</FormGroup>
)}
</FastField>
<AccountsTypesSelect
name={'account_type'}
items={accountsTypes}
onItemSelect={(accountType) => {
setFieldValue('account_type', accountType.key);
setFieldValue('currency_code', '');
}}
disabled={fieldsDisabled.accountType}
popoverProps={{ minimal: true }}
fastField={true}
fill={true}
/>
</FFormGroup>
<FFormGroup
name={'name'}
label={<T id={'account_name'} />}
labelInfo={<FieldRequiredHint />}
inline={true}
fastField={true}
>
<FInputGroup
medium={true}
inputRef={(ref) => (accountNameFieldRef.current = ref)}
name={'name'}
fastField={true}
/>
</FFormGroup>
<FFormGroup
label={<T id={'account_code'} />}
name={'code'}
labelInfo={<Hint content={<T id="account_code_hint" />} />}
inline={true}
fastField={true}
>
<FInputGroup medium={true} name={'code'} fastField={true} />
</FFormGroup>
<FFormGroup
label={' '}
name={'subaccount'}
inline={true}
fastField={true}
>
<FCheckbox
inline={true}
label={<T id={'sub_account'} />}
name={'subaccount'}
fastField={true}
/>
</FFormGroup>
{values.subaccount && (
<FFormGroup
name={'parent_account_id'}
shouldUpdate={parentAccountShouldUpdate}
label={<T id={'parent_account'} />}
inline={true}
fastField={true}
>
<AccountsSelect
name={'parent_account_id'}
items={accounts}
shouldUpdate={parentAccountShouldUpdate}
placeholder={<T id={'select_parent_account'} />}
filterByTypes={values.account_type}
buttonProps={{ disabled: !values.subaccount }}
fastField={true}
fill={true}
allowCreate={true}
/>
</FFormGroup>
)}
<If condition={FOREIGN_CURRENCY_ACCOUNTS.includes(values.account_type)}>
{/*------------ Currency -----------*/}
<FastField name={'currency_code'}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'currency'} />}
className={classNames('form-group--select-list', Classes.FILL)}
inline={true}
>
<CurrencySelect
name={'currency_code'}
currencies={currencies}
popoverProps={{ minimal: true }}
/>
</FormGroup>
)}
</FastField>
<FFormGroup
label={<T id={'currency'} />}
name={'currency_code'}
inline={true}
fastField={true}
>
<CurrencySelect
name={'currency_code'}
currencies={currencies}
popoverProps={{ minimal: true }}
fastField={true}
fill={true}
/>
</FFormGroup>
</If>
<FastField name={'description'}>
{({ field, meta: { error, touched } }) => (
<FormGroup
label={<T id={'description'} />}
className={'form-group--description'}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'description'} />}
inline={true}
>
<TextArea growVertically={true} height={280} {...field} />
</FormGroup>
)}
</FastField>
<FFormGroup
label={<T id={'description'} />}
name={'description'}
inline={true}
fastField={true}
>
<FTextArea
name={'description'}
growVertically={true}
height={280}
fill={true}
fastField={true}
/>
</FFormGroup>
</div>
<div className={Classes.DIALOG_FOOTER}>

View File

@@ -16,7 +16,8 @@ import { customersFieldShouldUpdate, accountsFieldShouldUpdate } from './utils';
import {
CurrencySelectList,
CustomerSelectField,
AccountsSelectList,
FFormGroup,
AccountsSelect,
FieldRequiredHint,
Hint,
} from '@/components';
@@ -54,37 +55,26 @@ export default function ExpenseFormHeader() {
)}
</FastField>
<FastField
<FFormGroup
name={'payment_account_id'}
accounts={accounts}
items={accounts}
label={<T id={'payment_account'} />}
labelInfo={<FieldRequiredHint />}
inline={true}
fastField={true}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'payment_account'} />}
className={classNames(
'form-group--payment_account',
'form-group--select-list',
Classes.FILL,
)}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'payment_account_id'} />}
inline={true}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={(account) => {
form.setFieldValue('payment_account_id', account.id);
}}
defaultSelectText={<T id={'select_payment_account'} />}
selectedAccountId={value}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
allowCreate={true}
/>
</FormGroup>
)}
</FastField>
<AccountsSelect
name={'payment_account_id'}
items={accounts}
placeholder={<T id={'select_payment_account'} />}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
allowCreate={true}
fastField={true}
shouldUpdate={accountsFieldShouldUpdate}
fill={true}
/>
</FFormGroup>
<FastField name={'currency_code'}>
{({ form, field: { value }, meta: { error, touched } }) => (

View File

@@ -114,7 +114,7 @@ export const customersFieldShouldUpdate = (newProps, oldProps) => {
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { Classes } from '@blueprintjs/core';
import {
AccountMultiSelect,
AccountsMultiSelect,
Row,
Col,
FormattedMessage as T,
@@ -54,7 +54,7 @@ function GLHeaderGeneralPaneContent() {
name={'accountsIds'}
className={Classes.FILL}
>
<AccountMultiSelect name="accountsIds" accounts={accounts} />
<AccountsMultiSelect name="accountsIds" items={accounts} />
</FFormGroup>
</Col>
</Row>

View File

@@ -9,15 +9,15 @@ import {
ControlGroup,
} from '@blueprintjs/core';
import {
AccountsSelectList,
AccountsSelect,
MoneyInputGroup,
Col,
Row,
Hint,
InputPrependText,
FFormGroup,
} from '@/components';
import { FormattedMessage as T } from '@/components';
import classNames from 'classnames';
import { useItemFormContext } from './ItemFormProvider';
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
@@ -91,42 +91,29 @@ function ItemFormBody({ organization: { base_currency } }) {
</FastField>
{/*------------- Selling account ------------- */}
<FastField
<FFormGroup
label={<T id={'account'} />}
name={'sell_account_id'}
labelInfo={
<Hint content={<T id={'item.field.sell_account.hint'} />} />
}
inline={true}
items={accounts}
sellable={values.sellable}
accounts={accounts}
shouldUpdate={sellAccountFieldShouldUpdate}
fastField={true}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'account'} />}
labelInfo={
<Hint content={<T id={'item.field.sell_account.hint'} />} />
}
inline={true}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="sell_account_id" />}
className={classNames(
'form-group--sell-account',
'form-group--select-list',
Classes.FILL,
)}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={(account) => {
form.setFieldValue('sell_account_id', account.id);
}}
defaultSelectText={<T id={'select_account'} />}
selectedAccountId={value}
disabled={!form.values.sellable}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
popoverFill={true}
allowCreate={true}
/>
</FormGroup>
)}
</FastField>
<AccountsSelect
name={'sell_account_id'}
items={accounts}
placeholder={<T id={'select_account'} />}
disabled={!values.sellable}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
fill={true}
allowCreate={true}
fastField={true}
/>
</FFormGroup>
<FastField
name={'sell_description'}
@@ -200,42 +187,31 @@ function ItemFormBody({ organization: { base_currency } }) {
</FastField>
{/*------------- Cost account ------------- */}
<FastField
<FFormGroup
name={'cost_account_id'}
purchasable={values.purchasable}
accounts={accounts}
items={accounts}
shouldUpdate={costAccountFieldShouldUpdate}
label={<T id={'account'} />}
labelInfo={
<Hint content={<T id={'item.field.cost_account.hint'} />} />
}
inline={true}
fastField={true}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'account'} />}
labelInfo={
<Hint content={<T id={'item.field.cost_account.hint'} />} />
}
inline={true}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="cost_account_id" />}
className={classNames(
'form-group--cost-account',
'form-group--select-list',
Classes.FILL,
)}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={(account) => {
form.setFieldValue('cost_account_id', account.id);
}}
defaultSelectText={<T id={'select_account'} />}
selectedAccountId={value}
disabled={!form.values.purchasable}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
popoverFill={true}
allowCreate={true}
/>
</FormGroup>
)}
</FastField>
<AccountsSelect
name={'cost_account_id'}
items={accounts}
placeholder={<T id={'select_account'} />}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
popoverFill={true}
allowCreate={true}
fastField={true}
disabled={!values.purchasable}
purchasable={values.purchasable}
shouldUpdate={costAccountFieldShouldUpdate}
/>
</FFormGroup>
<FastField
name={'purchase_description'}

View File

@@ -1,21 +1,18 @@
// @ts-nocheck
import React from 'react';
import { FastField, ErrorMessage } from 'formik';
import { FormGroup } from '@blueprintjs/core';
import { CLASSES } from '@/constants/classes';
import {
AccountsSelectList,
AccountsSelect,
FFormGroup,
FormattedMessage as T,
Col,
Row,
} from '@/components';
import classNames from 'classnames';
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
import withCurrentOrganization from '@/containers/Organization/withCurrentOrganization';
import { accountsFieldShouldUpdate } from './utils';
import { compose, inputIntent } from '@/utils';
import { ACCOUNT_TYPE } from '@/constants/accountTypes';
import { useItemFormContext } from './ItemFormProvider';
import { compose } from '@/utils';
/**
* Item form inventory sections.
@@ -31,36 +28,24 @@ function ItemFormInventorySection({ organization: { base_currency } }) {
<Row>
<Col xs={6}>
{/*------------- Inventory account ------------- */}
<FastField
{/*------------- Inventory Account ------------- */}
<FFormGroup
label={<T id={'inventory_account'} />}
name={'inventory_account_id'}
accounts={accounts}
items={accounts}
fastField={true}
shouldUpdate={accountsFieldShouldUpdate}
inline={true}
>
{({ form, field: { value }, meta: { touched, error } }) => (
<FormGroup
label={<T id={'inventory_account'} />}
inline={true}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name="inventory_account_id" />}
className={classNames(
'form-group--item-inventory_account',
'form-group--select-list',
CLASSES.FILL,
)}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={(account) => {
form.setFieldValue('inventory_account_id', account.id);
}}
defaultSelectText={<T id={'select_account'} />}
selectedAccountId={value}
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
/>
</FormGroup>
)}
</FastField>
<AccountsSelect
name={'inventory_account_id'}
items={accounts}
placeholder={<T id={'select_account'} />}
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
fastField={true}
shouldUpdate={accountsFieldShouldUpdate}
/>
</FFormGroup>
</Col>
</Row>
</div>

View File

@@ -119,7 +119,7 @@ export const handleDeleteErrors = (errors) => {
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};
@@ -149,7 +149,7 @@ export const sellPriceFieldShouldUpdate = (newProps, oldProps) => {
*/
export const sellAccountFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.items !== oldProps.items ||
newProps.sellable !== oldProps.sellable ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);

View File

@@ -2,6 +2,7 @@
import React from 'react';
import intl from 'react-intl-universal';
import { Form, FastField, useFormikContext } from 'formik';
import styled from 'styled-components';
import {
FormGroup,
RadioGroup,
@@ -14,13 +15,13 @@ import { useHistory } from 'react-router-dom';
import {
FormattedMessage as T,
AccountsSelectList,
AccountsSelect,
FieldRequiredHint,
CardFooterActions,
FFormGroup,
} from '@/components';
import { handleStringChange, inputIntent } from '@/utils';
import { ACCOUNT_TYPE } from '@/constants/accountTypes';
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from '@/constants/accountTypes';
import { useAccountantFormContext } from './AccountantFormProvider';
/**
@@ -28,6 +29,7 @@ import { useAccountantFormContext } from './AccountantFormProvider';
*/
export default function AccountantForm() {
const history = useHistory();
const { accounts } = useAccountantFormContext();
const { isSubmitting } = useFormikContext();
@@ -35,8 +37,6 @@ export default function AccountantForm() {
history.go(-1);
};
const { accounts } = useAccountantFormContext();
return (
<Form>
{/* ----------- Accounts ----------- */}
@@ -48,7 +48,7 @@ export default function AccountantForm() {
}
className={'accounts-checkbox'}
>
{/*------------ account code required -----------*/}
{/*------------ Account code (required) -----------*/}
<FastField name={'account_code_required'} type={'checkbox'}>
{({ field }) => (
<FormGroup inline={true}>
@@ -65,7 +65,8 @@ export default function AccountantForm() {
</FormGroup>
)}
</FastField>
{/*------------ account code unique -----------*/}
{/*------------ Account code (unique) -----------*/}
<FastField name={'account_code_unique'} type={'checkbox'}>
{({ field }) => (
<FormGroup inline={true}>
@@ -85,6 +86,7 @@ export default function AccountantForm() {
)}
</FastField>
</FormGroup>
{/* ----------- Accounting basis ----------- */}
<FastField name={'accounting_basis'}>
{({
@@ -116,120 +118,93 @@ export default function AccountantForm() {
</FastField>
{/* ----------- Deposit customer account ----------- */}
<FastField name={'preferred_deposit_account'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'deposit_customer_account'} />
</strong>
<AccountantFormGroup
name={'preferred_deposit_account'}
label={
<strong>
<T id={'deposit_customer_account'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('preferred_deposit_account', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
]}
/>
</FormGroup>
)}
</FastField>
/>
}
labelInfo={<FieldRequiredHint />}
fastField={true}
>
<AccountsSelect
name={'preferred_deposit_account'}
items={accounts}
placeholder={<T id={'select_payment_account'} />}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
]}
fastField={true}
/>
</AccountantFormGroup>
{/* ----------- Withdrawal vendor account ----------- */}
<FastField name={'withdrawal_account'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'withdrawal_vendor_account'} />
</strong>
<AccountantFormGroup
name={'withdrawal_account'}
label={
<strong>
<T id={'withdrawal_vendor_account'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('withdrawal_account', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
]}
/>
</FormGroup>
)}
</FastField>
/>
}
labelInfo={<FieldRequiredHint />}
fastField={true}
>
<AccountsSelect
name={'withdrawal_account'}
items={accounts}
placeholder={<T id={'select_payment_account'} />}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
]}
fastField={true}
/>
</AccountantFormGroup>
{/* ----------- Withdrawal customer account ----------- */}
<FastField name={'preferred_advance_deposit'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'customer_advance_deposit'} />
</strong>
<AccountantFormGroup
name={'preferred_advance_deposit'}
label={
<strong>
<T id={'customer_advance_deposit'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_vendor_advanced_deposits'
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_vendor_advanced_deposits'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('preferred_advance_deposit', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
// filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
/>
</FormGroup>
)}
</FastField>
/>
}
labelInfo={<FieldRequiredHint />}
fastField={true}
>
<AccountsSelect
name={'preferred_advance_deposit'}
items={accounts}
placeholder={<T id={'select_payment_account'} />}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.CURRENT_ASSET]}
fastField={true}
/>
</AccountantFormGroup>
<CardFooterActions>
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">
@@ -242,3 +217,7 @@ export default function AccountantForm() {
</Form>
);
}
const AccountantFormGroup = styled(FFormGroup)`
width: 450px;
`;

View File

@@ -1,15 +1,15 @@
// @ts-nocheck
import React from 'react';
import { Form, FastField, useFormikContext } from 'formik';
import { Form, useFormikContext } from 'formik';
import { FormGroup, Button, Intent } from '@blueprintjs/core';
import { useHistory } from 'react-router-dom';
import {
AccountsSelectList,
AccountsSelect,
FieldRequiredHint,
FormattedMessage as T,
CardFooterActions
FFormGroup,
CardFooterActions,
} from '@/components';
import { inputIntent } from '@/utils';
import { ACCOUNT_PARENT_TYPE, ACCOUNT_TYPE } from '@/constants/accountTypes';
import { useItemPreferencesFormContext } from './ItemPreferencesFormProvider';
@@ -29,113 +29,83 @@ export default function ItemForm() {
return (
<Form>
{/* ----------- preferred sell account ----------- */}
<FastField name={'preferred_sell_account'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'preferred_sell_account'} />
</strong>
{/* ----------- Preferred Sell Account ----------- */}
<FormGroup
name={'preferred_sell_account'}
label={
<strong>
<T id={'preferred_sell_account'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('preferred_sell_account', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
/>
</FormGroup>
)}
</FastField>
/>
}
labelInfo={<FieldRequiredHint />}
fastField={true}
>
<AccountsSelect
name={'preferred_sell_account'}
items={accounts}
placeholder={<T id={'select_payment_account'} />}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.INCOME]}
/>
</FormGroup>
{/* ----------- preferred cost account ----------- */}
<FastField name={'preferred_cost_account'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'preferred_cost_account'} />
</strong>
{/* ----------- Preferred Cost Account ----------- */}
<FFormGroup
name={'preferred_cost_account'}
label={
<strong>
<T id={'preferred_cost_account'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_after_customer_make_payment'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('preferred_cost_account', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
/>
</FormGroup>
)}
</FastField>
/>
}
labelInfo={<FieldRequiredHint />}
fastField={true}
>
<AccountsSelect
name={'preferred_cost_account'}
items={accounts}
placeholder={<T id={'select_payment_account'} />}
filterByParentTypes={[ACCOUNT_PARENT_TYPE.EXPENSE]}
/>
</FFormGroup>
{/* ----------- preferred inventory account ----------- */}
<FastField name={'preferred_inventory_account'}>
{({
form: { values, setFieldValue },
field: { value },
meta: { error, touched },
}) => (
<FormGroup
label={
<strong>
<T id={'preferred_inventory_account'} />
</strong>
{/* ----------- Preferred Inventory Account ----------- */}
<FFormGroup
name={'preferred_inventory_account'}
label={
<strong>
<T id={'preferred_inventory_account'} />
</strong>
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_vendor_advanced_deposits'
}
helperText={
<T
id={
'select_a_preferred_account_to_deposit_into_it_vendor_advanced_deposits'
}
/>
}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={({ id }) => {
setFieldValue('preferred_inventory_account', id);
}}
selectedAccountId={value}
defaultSelectText={<T id={'select_payment_account'} />}
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
/>
</FormGroup>
)}
</FastField>
/>
}
labelInfo={<FieldRequiredHint />}
fastField={true}
>
<AccountsSelect
name={'preferred_inventory_account'}
items={accounts}
placeholder={<T id={'select_payment_account'} />}
filterByTypes={[ACCOUNT_TYPE.INVENTORY]}
/>
</FFormGroup>
<CardFooterActions>
<Button intent={Intent.PRIMARY} loading={isSubmitting} type="submit">

View File

@@ -18,7 +18,7 @@ import { CLASSES } from '@/constants/classes';
import {
FFormGroup,
AccountsSelectList,
AccountsSelect,
VendorSelectField,
FieldRequiredHint,
InputPrependText,
@@ -211,41 +211,30 @@ function PaymentMadeFormHeaderFields({ organization: { base_currency } }) {
</FastField>
{/* ------------ Payment account ------------ */}
<FastField
<FFormGroup
name={'payment_account_id'}
accounts={accounts}
label={<T id={'payment_account'} />}
labelInfo={<FieldRequiredHint />}
items={accounts}
shouldUpdate={accountsFieldShouldUpdate}
inline={true}
fastField={true}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'payment_account'} />}
className={classNames(
'form-group--payment_account_id',
'form-group--select-list',
Classes.FILL,
)}
inline={true}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'payment_account_id'} />}
>
<AccountsSelectList
accounts={accounts}
labelInfo={<FieldRequiredHint />}
onAccountSelected={(account) => {
form.setFieldValue('payment_account_id', account.id);
}}
defaultSelectText={<T id={'select_payment_account'} />}
selectedAccountId={value}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
]}
/>
</FormGroup>
)}
</FastField>
<AccountsSelect
name={'payment_account_id'}
items={accounts}
placeholder={<T id={'select_payment_account'} />}
labelInfo={<FieldRequiredHint />}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
]}
shouldUpdate={accountsFieldShouldUpdate}
fastField={true}
fill={true}
/>
</FFormGroup>
{/* ------------ Reference ------------ */}
<FastField name={'reference'}>

View File

@@ -84,7 +84,7 @@ export const vendorsFieldShouldUpdate = (newProps, oldProps) => {
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -16,21 +16,21 @@ function InvoicesListProvider({ query, tableStateChanged, ...props }) {
const { data: invoicesViews, isLoading: isViewsLoading } =
useResourceViews('sale_invoices');
// Fetch the accounts resource fields.
// Fetch resource fields of the sale invoices.
const {
data: resourceMeta,
isLoading: isResourceLoading,
isFetching: isResourceFetching,
} = useResourceMeta('sale_invoices');
// Fetch accounts list according to the given custom view id.
// Fetch sale invoices of the given query.
const {
data: { invoices, pagination, filterMeta },
isFetching: isInvoicesFetching,
isLoading: isInvoicesLoading,
} = useInvoices(query, { keepPreviousData: true });
// Detarmines the datatable empty status.
// Detarmines whether the table should show empty state.
const isEmptyStatus =
isEmpty(invoices) && !tableStateChanged && !isInvoicesLoading;

View File

@@ -27,7 +27,7 @@ import {
} from '@/utils';
import {
FFormGroup,
AccountsSelectList,
AccountsSelect,
CustomerSelectField,
FieldRequiredHint,
Icon,
@@ -285,41 +285,30 @@ function PaymentReceiveHeaderFields({
</FastField>
{/* ------------ Deposit account ------------ */}
<FastField
<FFormGroup
name={'deposit_account_id'}
accounts={accounts}
label={<T id={'deposit_to'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
items={accounts}
shouldUpdate={accountsFieldShouldUpdate}
fastField={true}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'deposit_to'} />}
className={classNames(
'form-group--deposit_account_id',
'form-group--select-list',
CLASSES.FILL,
)}
inline={true}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'deposit_account_id'} />}
>
<AccountsSelectList
accounts={accounts}
labelInfo={<FieldRequiredHint />}
onAccountSelected={(account) => {
form.setFieldValue('deposit_account_id', account.id);
}}
defaultSelectText={<T id={'select_deposit_account'} />}
selectedAccountId={value}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
]}
/>
</FormGroup>
)}
</FastField>
<AccountsSelect
name={'deposit_account_id'}
items={accounts}
labelInfo={<FieldRequiredHint />}
placeholder={<T id={'select_deposit_account'} />}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
]}
shouldUpdate={accountsFieldShouldUpdate}
fastField={true}
fill={true}
/>
</FFormGroup>
{/* ------------ Reference No. ------------ */}
<FastField name={'reference_no'}>

View File

@@ -150,7 +150,7 @@ export const customersFieldShouldUpdate = (newProps, oldProps) => {
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -15,7 +15,7 @@ import { CLASSES } from '@/constants/classes';
import {
FFormGroup,
AccountsSelectList,
AccountsSelect,
CustomerSelectField,
FieldRequiredHint,
Icon,
@@ -125,38 +125,30 @@ function ReceiptFormHeader({
/>
{/* ----------- Deposit account ----------- */}
<FastField
<FFormGroup
label={<T id={'deposit_account'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
name={'deposit_account_id'}
accounts={accounts}
items={accounts}
fastField={true}
shouldUpdate={accountsFieldShouldUpdate}
>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'deposit_account'} />}
className={classNames('form-group--deposit-account', CLASSES.FILL)}
inline={true}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'deposit_account_id'} />}
>
<AccountsSelectList
accounts={accounts}
onAccountSelected={(account) => {
form.setFieldValue('deposit_account_id', account.id);
}}
defaultSelectText={<T id={'select_deposit_account'} />}
selectedAccountId={value}
popoverFill={true}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
]}
allowCreate={true}
/>
</FormGroup>
)}
</FastField>
<AccountsSelect
items={accounts}
name={'deposit_account_id'}
placeholder={<T id={'select_deposit_account'} />}
filterByTypes={[
ACCOUNT_TYPE.CASH,
ACCOUNT_TYPE.BANK,
ACCOUNT_TYPE.OTHER_CURRENT_ASSET,
]}
allowCreate={true}
fill={true}
fastField={true}
shouldUpdate={accountsFieldShouldUpdate}
/>
</FFormGroup>
{/* ----------- Receipt date ----------- */}
<FastField name={'receipt_date'}>

View File

@@ -101,7 +101,7 @@ export const entriesFieldShouldUpdate = (newProps, oldProps) => {
*/
export const accountsFieldShouldUpdate = (newProps, oldProps) => {
return (
newProps.accounts !== oldProps.accounts ||
newProps.items !== oldProps.items ||
defaultFastFieldShouldUpdate(newProps, oldProps)
);
};

View File

@@ -20,16 +20,16 @@ function VendorsListProvider({ tableState, tableStateChanged, ...props }) {
isFetching: isVendorsFetching,
} = useVendors(tableQuery, { keepPreviousData: true });
// Fetch customers resource views and fields.
// Fetch vendors resource views and fields.
const { data: vendorsViews, isLoading: isVendorsViewsLoading } =
useResourceViews('vendors');
// Fetch the customers resource fields.
// Fetch the vendors resource fields.
const {
data: resourceMeta,
isLoading: isResourceMetaLoading,
isFetching: isResourceMetaFetching,
} = useResourceMeta('customers');
} = useResourceMeta('vendors');
// Detarmines the datatable empty status.
const isEmptyStatus =

View File

@@ -8,13 +8,13 @@ import {
} from '@blueprintjs/core';
import { DateInput } from '@blueprintjs/datetime';
import { FastField, Field, ErrorMessage } from 'formik';
import { FormattedMessage as T } from '@/components';
import { FFormGroup, FormattedMessage as T } from '@/components';
import { momentFormatter, compose, tansformDateValue } from '@/utils';
import classNames from 'classnames';
import { CLASSES } from '@/constants/classes';
import {
AccountsSelectList,
AccountsSelect,
FieldRequiredHint,
Icon,
InputPrependButton,
@@ -91,6 +91,7 @@ function WarehouseTransferFormHeaderFields({
</FormGroup>
)}
</FastField>
{/* ----------- Transfer number ----------- */}
<Field name={'transaction_number'}>
{({ form, field, meta: { error, touched } }) => (
@@ -130,60 +131,39 @@ function WarehouseTransferFormHeaderFields({
</FormGroup>
)}
</Field>
{/* ----------- Form Warehouse ----------- */}
<FastField name={'from_warehouse_id'} accounts={warehouses}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'warehouse_transfer.label.from_warehouse'} />}
className={classNames(
'form-group--warehouse-transfer',
CLASSES.FILL,
)}
inline={true}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'from_warehouse_id'} />}
>
<AccountsSelectList
accounts={warehouses}
onAccountSelected={(account) => {
form.setFieldValue('from_warehouse_id', account.id);
}}
defaultSelectText={<T id={'select_warehouse_transfer'} />}
selectedAccountId={value}
popoverFill={true}
allowCreate={true}
/>
</FormGroup>
)}
</FastField>
<FFormGroup
name={'from_warehouse_id'}
items={warehouses}
label={<T id={'warehouse_transfer.label.from_warehouse'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
>
<AccountsSelect
name={'from_warehouse_id'}
items={warehouses}
placeholder={<T id={'select_warehouse_transfer'} />}
allowCreate={true}
fill={true}
/>
</FFormGroup>
{/* ----------- To Warehouse ----------- */}
<FastField name={'to_warehouse_id'} accounts={warehouses}>
{({ form, field: { value }, meta: { error, touched } }) => (
<FormGroup
label={<T id={'warehouse_transfer.label.to_warehouse'} />}
className={classNames(
'form-group--warehouse-transfer',
CLASSES.FILL,
)}
inline={true}
labelInfo={<FieldRequiredHint />}
intent={inputIntent({ error, touched })}
helperText={<ErrorMessage name={'to_warehouse_id'} />}
>
<AccountsSelectList
accounts={warehouses}
onAccountSelected={(account) => {
form.setFieldValue('to_warehouse_id', account.id);
}}
defaultSelectText={<T id={'select_warehouse_transfer'} />}
selectedAccountId={value}
popoverFill={true}
allowCreate={true}
/>
</FormGroup>
)}
</FastField>
<FFormGroup
name={'to_warehouse_id'}
label={<T id={'warehouse_transfer.label.to_warehouse'} />}
inline={true}
labelInfo={<FieldRequiredHint />}
>
<AccountsSelect
name={'to_warehouse_id'}
items={warehouses}
placeholder={<T id={'select_warehouse_transfer'} />}
fill={true}
allowCreate={true}
/>
</FFormGroup>
</div>
);
}

View File

@@ -2,6 +2,8 @@
import { useMutation } from 'react-query';
import useApiRequest from '../useRequest';
import { setCookie } from '../../utils';
import { useRequestQuery } from '../useQueryRequest';
import t from './types';
/**
* Saves the response data to cookies.
@@ -70,3 +72,21 @@ export const useAuthResetPassword = (props) => {
props,
);
};
/**
* Fetches the authentication page metadata.
*/
export const useAuthMetadata = (props) => {
return useRequestQuery(
[t.AUTH_METADATA_PAGE,],
{
method: 'get',
url: `auth/meta`,
},
{
select: (res) => res.data,
defaultData: {},
...props,
},
);
}

View File

@@ -1,4 +1,8 @@
// @ts-nocheck
const Authentication = {
AUTH_METADATA_PAGE: 'AUTH_META_PAGE'
}
const ACCOUNTS = {
ACCOUNT: 'ACCOUNT',
ACCOUNT_TRANSACTION: 'ACCOUNT_TRANSACTION',
@@ -217,6 +221,7 @@ const DASHBOARD = {
};
export default {
...Authentication,
...ACCOUNTS,
...BILLS,
...VENDORS,

View File

@@ -64,7 +64,7 @@
"new_currency": "New Currency",
"currency_name": "Currency Name",
"currency_code": "Currency Code",
"select_currency_code": "Select Currency Code",
"select_currency_code": "Select Currency Code...",
"edit_exchange_rate": "Edit Exchange Rate",
"new_exchange_rate": "New Exchange Rate",
"delete_exchange_rate": "Delete Exchange Rate",
@@ -242,7 +242,7 @@
"organization": "Organization.",
"check_your_email_for_a_link_to_reset": "Check your email for a link to reset your password.If it doesnt appear within a few minutes, check your spam folder.",
"we_couldn_t_find_your_account_with_that_email": "We couldn't find your account with that email.",
"select_parent_account": "Select Parent Account",
"select_parent_account": "Select Parent Account...",
"the_exchange_rate_has_been_edited_successfully": "The exchange rate has been edited successfully",
"the_exchange_rate_has_been_created_successfully": "The exchange rate has been created successfully",
"the_user_details_has_been_updated": "The user details has been updated",
@@ -540,7 +540,7 @@
"statement": "Statement",
"deposit_account": "Deposit Account",
"send_to_email": "Send to email",
"select_deposit_account": "Select Deposit Account",
"select_deposit_account": "Select Deposit Account...",
"once_delete_this_receipt_you_will_able_to_restore_it": "Once you delete this receipt, you won't be able to restore it later. Are you sure you want to delete this receipt?",
"the_receipt_has_been_created_successfully": "The receipt #{number} has been created successfully.",
"the_receipt_has_been_edited_successfully": "The receipt #{number} has been edited successfully.",
@@ -609,8 +609,8 @@
"new_payment_made": "New Payment Made",
"payment_made_list": "Payment Made List",
"payment_account": "Payment Account",
"select_vender_account": "Select Vender Account",
"select_payment_account": "Select Payment Account",
"select_vender_account": "Select Vender Account...",
"select_payment_account": "Select Payment Account...",
"the_payment_made_has_been_edited_successfully": "The payment made has been edited successfully.",
"the_payment_made_has_been_created_successfully": "The payment made has been created successfully.",
"the_payment_made_has_been_deleted_successfully": "The payment made has been deleted successfully.",
@@ -1417,6 +1417,7 @@
"siebar.cashflow": "Cash flow",
"siebar.cashflow.label_cash_and_bank_accounts": "Cash & Bank Accounts",
"cash_flow.label_account_transcations": "Account Transcations",
"cash_flow.transactions_for_review": "Transactions for review",
"cash_flow.label.deposit": "Deposit",
"cash_flow.label.withdrawal": "Withdrawal",
"cash_flow.label.running_balance": "Running balance",

View File

@@ -52,6 +52,7 @@ body.hide-scrollbar .Pane2 {
}
.bp3-fill {
.bp3-popover-wrapper,
.bp3-popover-target {
display: block;
@@ -68,9 +69,9 @@ body.hide-scrollbar .Pane2 {
margin-right: 6px;
}
.bp3-select-popover .bp3-menu {
.bp3-select-popover .bp3-menu,
.bp3-multi-select-popover .bp3-menu {
max-height: 300px;
max-width: 400px;
overflow: auto;
}
@@ -103,27 +104,21 @@ body.hide-scrollbar .Pane2 {
background-color: #0066ff;
}
.ReactVirtualized__Collection {
}
.ReactVirtualized__Collection {}
.ReactVirtualized__Collection__innerScrollContainer {
}
.ReactVirtualized__Collection__innerScrollContainer {}
/* Grid default theme */
.ReactVirtualized__Grid {
}
.ReactVirtualized__Grid {}
.ReactVirtualized__Grid__innerScrollContainer {
}
.ReactVirtualized__Grid__innerScrollContainer {}
/* Table default theme */
.ReactVirtualized__Table {
}
.ReactVirtualized__Table {}
.ReactVirtualized__Table__Grid {
}
.ReactVirtualized__Table__Grid {}
.ReactVirtualized__Table__headerRow {
font-weight: 700;
@@ -186,8 +181,7 @@ body.hide-scrollbar .Pane2 {
/* List default theme */
.ReactVirtualized__List {
}
.ReactVirtualized__List {}
.bp3-drawer {
box-shadow: 0 0 0;
@@ -243,7 +237,7 @@ html[lang^='ar'] {
padding-left: 14px;
padding-right: 14px;
& + .bp3-button {
&+.bp3-button {
margin-left: 8px;
}
}
@@ -255,7 +249,7 @@ html[lang^='ar'] {
margin: 0;
}
> .bp3-spinner {
>.bp3-spinner {
margin: 20px 0;
}
}
@@ -283,6 +277,7 @@ html[lang^='ar'] {
.align-right {
text-align: right;
}
.align-center {
text-align: center;
}
@@ -292,6 +287,6 @@ html[lang^='ar'] {
}
span.table-tooltip-overview-target{
span.table-tooltip-overview-target {
display: inline;
}