@accounter/client 0.0.8-alpha-20251021150615-800574fc6d416cd319de216c97b431643d8958a2 → 0.0.8-alpha-20251021225827-178e480c997a9811913e16f85cb94329041b096e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -1
- package/dist/assets/Checkbox-CxedbJAl.js +6 -0
- package/dist/assets/Progress-D5SuJtCd.js +1 -0
- package/dist/assets/Typography-BQFz-z7L.js +1 -0
- package/dist/assets/accordion-COWOBKuq.js +1 -0
- package/dist/assets/accountant-approvals-Bd2y8us_.js +1 -0
- package/dist/assets/all-charges-SWBnaZu7.js +1 -0
- package/dist/assets/arrow-up-down-dZmrBLse.js +6 -0
- package/dist/assets/business--GVVfDEa.js +37 -0
- package/dist/assets/business-transactions-single-BsbkUf_H.js +1 -0
- package/dist/assets/business-trip-ByXPVXdG.js +1 -0
- package/dist/assets/charges-filters-D43UbXob.js +1 -0
- package/dist/assets/charges-ledger-validation-D0uMH_JE.js +1 -0
- package/dist/assets/chart-ClU1KbWe.js +74 -0
- package/dist/assets/data-table-pagination-D9Y0_Tn8.js +11 -0
- package/dist/assets/editable-business-trip-DhqOQBPa.js +16 -0
- package/dist/assets/graphql-document-dedupe-fragments-ByT8-wlV.js +1 -0
- package/dist/assets/index-1U6rQgQe.js +6 -0
- package/dist/assets/index-3-AKn8tg.js +1 -0
- package/dist/assets/index-91A2PLZ6.js +137 -0
- package/dist/assets/index-BBHuCWRn.js +1 -0
- package/dist/assets/index-BPNuFFtx.js +1 -0
- package/dist/assets/index-BXqHnRVY.js +1 -0
- package/dist/assets/index-BciOH8FS.js +1 -0
- package/dist/assets/index-BjHuUHDO.js +1 -0
- package/dist/assets/index-BxKmoNQd.js +1 -0
- package/dist/assets/index-C3bqiFIv.js +2 -0
- package/dist/assets/index-C5MeepK_.js +11 -0
- package/dist/assets/index-CAwm68Mg.js +1 -0
- package/dist/assets/index-CJ8OGXxv.js +1 -0
- package/dist/assets/index-CJyY-qF6.js +1 -0
- package/dist/assets/index-CMYnx46_.js +6 -0
- package/dist/assets/index-CNrwxUZ7.js +1 -0
- package/dist/assets/index-CvV5z5r9.js +876 -0
- package/dist/assets/index-D08H2GXq.js +17 -0
- package/dist/assets/index-GFsPY1p4.js +2 -0
- package/dist/assets/index-KwNwThNu.js +1 -0
- package/dist/assets/index-YA8IBFyB.js +1 -0
- package/dist/assets/index-ZpyI3qxW.js +24 -0
- package/dist/assets/index-gdTXrWXt.css +1 -0
- package/dist/assets/index-ytnIEraq.js +9 -0
- package/dist/assets/{index.es-DHwHzag1.js → index.es-CYeQ4a5s.js} +5 -5
- package/dist/assets/issue-document-CdikNnO2.js +1 -0
- package/dist/assets/login-page-effgZS3V.js +1 -0
- package/dist/assets/missing-info-charges-CnPFTzoZ.js +1 -0
- package/dist/assets/page-not-found-D8YlgDOm.js +1 -0
- package/dist/assets/pencil-mxW0-tGM.js +6 -0
- package/dist/assets/report-commentary-row-DCozKgVE.js +1 -0
- package/dist/assets/save-CHlytUqu.js +11 -0
- package/dist/assets/sequential-CAnleQny.js +1 -0
- package/dist/assets/similar-charges-by-business-modal-Dzbspk_r.js +1 -0
- package/dist/assets/sub-Cp_PhKiD.js +1 -0
- package/dist/assets/subMonths-DCj_iXAn.js +1 -0
- package/dist/index.html +2 -2
- package/package.json +6 -5
- package/src/app.tsx +35 -25
- package/src/components/business/business-header.tsx +68 -0
- package/src/components/business/charges-section.tsx +82 -0
- package/src/components/business/charts-section.tsx +115 -0
- package/src/components/business/configurations-section.tsx +885 -0
- package/src/components/business/contact-info-section.tsx +536 -0
- package/src/components/business/contracts-section.tsx +196 -0
- package/src/components/business/documents-section.tsx +26 -0
- package/src/components/business/index.tsx +171 -0
- package/src/components/business/integrations-section.tsx +477 -0
- package/src/components/business/transactions-section.tsx +26 -0
- package/src/components/business-transactions/business-extended-info.tsx +22 -28
- package/src/components/business-transactions/business-transactions-single.tsx +3 -3
- package/src/components/business-transactions/index.tsx +13 -2
- package/src/components/business-trips/business-trip.tsx +3 -3
- package/src/components/charges/cells/business-trip.tsx +6 -8
- package/src/components/charges/cells/counterparty.tsx +7 -5
- package/src/components/charges/charge-extended-info-menu.tsx +27 -21
- package/src/components/charges/charges-row.tsx +12 -10
- package/src/components/charges/charges-table.tsx +15 -9
- package/src/components/clients/contracts/modify-contract-dialog.tsx +464 -0
- package/src/components/clients/modify-client-dialog.tsx +276 -0
- package/src/components/common/accounter-table.tsx +6 -5
- package/src/components/common/business-trip-report/parts/core-expense-row.tsx +11 -9
- package/src/components/common/business-trip-report/parts/uncategorized-transactions.tsx +11 -13
- package/src/components/common/buttons/index.ts +0 -2
- package/src/components/common/buttons/logout-button.tsx +7 -6
- package/src/components/common/documents/issue-document/index.tsx +3 -3
- package/src/components/common/documents/issue-document/{recent-client-docs.tsx → recent-business-docs.tsx} +19 -13
- package/src/components/common/documents-to-charge-matcher/selection-handler/index.tsx +4 -2
- package/src/components/common/documents-to-charge-matcher/selection-handler/wide-filtered-selection.tsx +5 -7
- package/src/components/common/forms/business-card.tsx +1 -0
- package/src/components/common/forms/edit-document.tsx +23 -10
- package/src/components/common/forms/modify-business-fields.tsx +2 -19
- package/src/components/common/inputs/combo-box.tsx +1 -1
- package/src/components/common/new-documents-list.tsx +10 -8
- package/src/components/documents-table/cells/creditor.tsx +11 -4
- package/src/components/documents-table/cells/debtor.tsx +11 -4
- package/src/components/error-boundary.tsx +189 -0
- package/src/components/layout/breadcrumbs.tsx +77 -0
- package/src/components/layout/dashboard-layout.tsx +4 -0
- package/src/components/layout/document-title.tsx +31 -0
- package/src/components/layout/navigation-progress.tsx +52 -0
- package/src/components/layout/page-skeleton.tsx +49 -0
- package/src/components/layout/sidelinks.tsx +28 -27
- package/src/components/ledger-table/counterparty-cell.tsx +19 -13
- package/src/components/login-page.tsx +2 -1
- package/src/components/reports/corporate-tax-ruling-compliance-report/index.tsx +3 -3
- package/src/components/reports/profit-and-loss-report/index.tsx +3 -3
- package/src/components/reports/tax-report/index.tsx +3 -3
- package/src/components/reports/trial-balance-report/trial-balance-report-group.tsx +4 -6
- package/src/components/reports/trial-balance-report/trial-balance-report-sort-code.tsx +8 -11
- package/src/components/screens/businesses/business.tsx +56 -0
- package/src/components/screens/charges/charge.tsx +22 -9
- package/src/components/screens/documents/issue-documents/edit-issue-document-modal.tsx +4 -4
- package/src/components/transactions-table/cells/counterparty.tsx +9 -2
- package/src/components/transactions-table/cells-legacy/counterparty.tsx +9 -2
- package/src/components/ui/progress.tsx +25 -0
- package/src/components/ui/skeleton.tsx +12 -0
- package/src/gql/gql.ts +93 -9
- package/src/gql/graphql.ts +289 -9
- package/src/helpers/contracts.ts +22 -0
- package/src/helpers/currency.ts +5 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/pcn874.ts +17 -0
- package/src/hooks/use-add-sort-code.ts +1 -1
- package/src/hooks/use-add-tag.ts +1 -1
- package/src/hooks/use-create-contract.ts +62 -0
- package/src/hooks/use-delete-contract.ts +64 -0
- package/src/hooks/use-delete-tag.ts +1 -1
- package/src/hooks/use-get-all-contracts.ts +0 -1
- package/src/hooks/use-insert-client.ts +80 -0
- package/src/hooks/use-merge-businesses.ts +1 -1
- package/src/hooks/use-merge-charges.ts +1 -1
- package/src/hooks/use-update-client.ts +75 -0
- package/src/hooks/use-update-contract.ts +69 -0
- package/src/index.tsx +4 -22
- package/src/providers/auth-guard.tsx +14 -23
- package/src/providers/index.tsx +7 -2
- package/src/providers/urql-client.ts +86 -0
- package/src/providers/urql.tsx +7 -12
- package/src/providers/user-provider.tsx +3 -2
- package/src/router/config.tsx +534 -0
- package/src/router/layouts/dashboard-layout.tsx +20 -0
- package/src/router/layouts/root-layout.tsx +69 -0
- package/src/router/loaders/auth-loader.ts +32 -0
- package/src/router/loaders/business-loader.ts +25 -0
- package/src/router/loaders/charge-loader.ts +25 -0
- package/src/router/loaders/index.ts +17 -0
- package/src/router/routes.ts +88 -0
- package/src/router/types.ts +62 -0
- package/dist/assets/index-0eCf1BcD.css +0 -1
- package/dist/assets/index-DHTbHvtz.js +0 -1188
- package/src/components/common/buttons/button-with-label.tsx +0 -41
- package/src/components/common/buttons/button.tsx +0 -44
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { BillingCycle, SubscriptionPlan } from '@/gql/graphql.js';
|
|
2
|
+
|
|
3
|
+
export function standardBillingCycle(billingCycle: BillingCycle): string {
|
|
4
|
+
switch (billingCycle) {
|
|
5
|
+
case BillingCycle.Annual:
|
|
6
|
+
return 'Annual';
|
|
7
|
+
case BillingCycle.Monthly:
|
|
8
|
+
return 'Monthly';
|
|
9
|
+
default:
|
|
10
|
+
throw new Error(`Unsupported Billing Cycle: ${billingCycle}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function standardPlan(plan: SubscriptionPlan): string {
|
|
14
|
+
switch (plan) {
|
|
15
|
+
case SubscriptionPlan.Enterprise:
|
|
16
|
+
return 'Enterprise';
|
|
17
|
+
case SubscriptionPlan.Pro:
|
|
18
|
+
return 'Pro';
|
|
19
|
+
default:
|
|
20
|
+
throw new Error(`Unsupported Subscription plan: ${plan}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/helpers/currency.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Currency } from '../gql/graphql.js';
|
|
2
|
+
import { formatStringifyAmount } from './index.js';
|
|
2
3
|
|
|
3
4
|
export function getCurrencyFormatter(
|
|
4
5
|
currency: Currency,
|
|
@@ -68,3 +69,7 @@ export const FIAT_CURRENCIES: Currency[] = [
|
|
|
68
69
|
Currency.Aud,
|
|
69
70
|
Currency.Sek,
|
|
70
71
|
] as const;
|
|
72
|
+
|
|
73
|
+
export function formatAmountWithCurrency(amount: number, currency: Currency, digits = 2): string {
|
|
74
|
+
return `${currencyCodeToSymbol(currency)} ${formatStringifyAmount(amount, digits)}`;
|
|
75
|
+
}
|
package/src/helpers/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from './charges.js';
|
|
2
2
|
export * from './clipboard.js';
|
|
3
3
|
export * from './consts.js';
|
|
4
|
+
export * from './contracts.js';
|
|
4
5
|
export * from './currency.js';
|
|
5
6
|
export * from './dates.js';
|
|
6
7
|
export * from './document-matches.js';
|
|
@@ -9,6 +10,7 @@ export * from './files.js';
|
|
|
9
10
|
export * from './form.js';
|
|
10
11
|
export * from './graphql-document-dedupe-fragments.js';
|
|
11
12
|
export * from './numbers.js';
|
|
13
|
+
export * from './pcn874.js';
|
|
12
14
|
export * from './strings-manipulations.js';
|
|
13
15
|
export * from './vat.js';
|
|
14
16
|
export * from './tags.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Pcn874RecordType } from '@/gql/graphql.js';
|
|
2
|
+
|
|
3
|
+
export const pcn874RecordEnum: Record<Pcn874RecordType, string> = {
|
|
4
|
+
C: 'INPUT_SELF_INVOICE',
|
|
5
|
+
H: 'INPUT_SINGLE_DOC_BY_LAW',
|
|
6
|
+
I: 'SALE_PALESTINIAN_CUSTOMER',
|
|
7
|
+
K: 'INPUT_PETTY_CASH',
|
|
8
|
+
L1: 'SALE_UNIDENTIFIED_CUSTOMER',
|
|
9
|
+
L2: 'SALE_UNIDENTIFIED_ZERO_OR_EXEMPT',
|
|
10
|
+
M: 'SALE_SELF_INVOICE',
|
|
11
|
+
P: 'INPUT_PALESTINIAN_SUPPLIER',
|
|
12
|
+
R: 'INPUT_IMPORT',
|
|
13
|
+
S1: 'SALE_REGULAR',
|
|
14
|
+
S2: 'SALE_ZERO_OR_EXEMPT',
|
|
15
|
+
T: 'INPUT_REGULAR',
|
|
16
|
+
Y: 'SALE_EXPORT',
|
|
17
|
+
} as const;
|
|
@@ -20,7 +20,7 @@ const NOTIFICATION_ID = 'addSortCode';
|
|
|
20
20
|
|
|
21
21
|
export const useAddSortCode = (): UseAddSortCode => {
|
|
22
22
|
// TODO: add authentication
|
|
23
|
-
// TODO: add local data update method after
|
|
23
|
+
// TODO: add local data update method after change
|
|
24
24
|
|
|
25
25
|
const [{ fetching }, mutate] = useMutation(AddSortCodeDocument);
|
|
26
26
|
const addSortCode = useCallback(
|
package/src/hooks/use-add-tag.ts
CHANGED
|
@@ -20,7 +20,7 @@ const NOTIFICATION_ID = 'addTag';
|
|
|
20
20
|
|
|
21
21
|
export const useAddTag = (): UseAddTag => {
|
|
22
22
|
// TODO: add authentication
|
|
23
|
-
// TODO: add local data update method after
|
|
23
|
+
// TODO: add local data update method after change
|
|
24
24
|
|
|
25
25
|
const [{ fetching }, mutate] = useMutation(AddTagDocument);
|
|
26
26
|
const addTag = useCallback(
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
import { useMutation } from 'urql';
|
|
4
|
+
import { CreateContractDocument, type CreateContractMutationVariables } from '../gql/graphql.js';
|
|
5
|
+
import { handleCommonErrors } from '../helpers/error-handling.js';
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
|
|
8
|
+
/* GraphQL */ `
|
|
9
|
+
mutation CreateContract($input: CreateContractInput!) {
|
|
10
|
+
createContract(input: $input) {
|
|
11
|
+
id
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
|
|
16
|
+
type UseCreateContract = {
|
|
17
|
+
creating: boolean;
|
|
18
|
+
createContract: (variables: CreateContractMutationVariables) => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const NOTIFICATION_ID = 'createContract';
|
|
22
|
+
|
|
23
|
+
export const useCreateContract = (): UseCreateContract => {
|
|
24
|
+
// TODO: add authentication
|
|
25
|
+
// TODO: add local data update method after change
|
|
26
|
+
|
|
27
|
+
const [{ fetching }, mutate] = useMutation(CreateContractDocument);
|
|
28
|
+
const createContract = useCallback(
|
|
29
|
+
async (variables: CreateContractMutationVariables) => {
|
|
30
|
+
const message = `Error creating new contract for client[${variables.input.clientId}]`;
|
|
31
|
+
const notificationId = `${NOTIFICATION_ID}-${variables.input.clientId}`;
|
|
32
|
+
toast.loading('Creating contract', {
|
|
33
|
+
id: notificationId,
|
|
34
|
+
});
|
|
35
|
+
try {
|
|
36
|
+
const res = await mutate(variables);
|
|
37
|
+
const data = handleCommonErrors(res, message, notificationId);
|
|
38
|
+
if (data) {
|
|
39
|
+
toast.success('Success', {
|
|
40
|
+
id: notificationId,
|
|
41
|
+
description: `Contract for client ${variables.input.clientId} was successfully created`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error(`${message}: ${e}`);
|
|
46
|
+
toast.error('Error', {
|
|
47
|
+
id: notificationId,
|
|
48
|
+
description: message,
|
|
49
|
+
duration: 100_000,
|
|
50
|
+
closeButton: true,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
},
|
|
55
|
+
[mutate],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
creating: fetching,
|
|
60
|
+
createContract,
|
|
61
|
+
};
|
|
62
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
import { useMutation } from 'urql';
|
|
4
|
+
import { DeleteContractDocument, type DeleteContractMutationVariables } from '../gql/graphql.js';
|
|
5
|
+
import { handleCommonErrors } from '../helpers/error-handling.js';
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
|
|
8
|
+
/* GraphQL */ `
|
|
9
|
+
mutation DeleteContract($contractId: UUID!) {
|
|
10
|
+
deleteContract(id: $contractId)
|
|
11
|
+
}
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
type UseDeleteContract = {
|
|
15
|
+
fetching: boolean;
|
|
16
|
+
deleteContract: (variables: DeleteContractMutationVariables) => Promise<boolean>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const NOTIFICATION_ID = 'deleteContract';
|
|
20
|
+
|
|
21
|
+
export const useDeleteContract = (): UseDeleteContract => {
|
|
22
|
+
// TODO: add authentication
|
|
23
|
+
// TODO: add local data delete method after change
|
|
24
|
+
|
|
25
|
+
const [{ fetching }, mutate] = useMutation(DeleteContractDocument);
|
|
26
|
+
const deleteContract = useCallback(
|
|
27
|
+
async (variables: DeleteContractMutationVariables) => {
|
|
28
|
+
const message = `Error deleting contract ID [${variables.contractId}]`;
|
|
29
|
+
const notificationId = `${NOTIFICATION_ID}-${variables.contractId}`;
|
|
30
|
+
toast.loading('Deleting contract', {
|
|
31
|
+
id: notificationId,
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
const res = await mutate(variables);
|
|
35
|
+
const data = handleCommonErrors(res, message, notificationId);
|
|
36
|
+
if (data) {
|
|
37
|
+
if (data.deleteContract === false) {
|
|
38
|
+
throw new Error('Unsuccessful deletion');
|
|
39
|
+
}
|
|
40
|
+
toast.success('Success', {
|
|
41
|
+
id: notificationId,
|
|
42
|
+
description: 'Contract was deleted',
|
|
43
|
+
});
|
|
44
|
+
return data.deleteContract;
|
|
45
|
+
}
|
|
46
|
+
} catch (e) {
|
|
47
|
+
console.error(`${message}: ${e}`);
|
|
48
|
+
toast.error('Error', {
|
|
49
|
+
id: notificationId,
|
|
50
|
+
description: message,
|
|
51
|
+
duration: 100_000,
|
|
52
|
+
closeButton: true,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
},
|
|
57
|
+
[mutate],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
fetching,
|
|
62
|
+
deleteContract,
|
|
63
|
+
};
|
|
64
|
+
};
|
|
@@ -20,7 +20,7 @@ const NOTIFICATION_ID = 'deleteTag';
|
|
|
20
20
|
|
|
21
21
|
export const useDeleteTag = (): UseDeleteTag => {
|
|
22
22
|
// TODO: add authentication
|
|
23
|
-
// TODO: add local data update method after
|
|
23
|
+
// TODO: add local data update method after change
|
|
24
24
|
|
|
25
25
|
const [{ fetching }, mutate] = useMutation(DeleteTagDocument);
|
|
26
26
|
const deleteTag = useCallback(
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
import { useMutation } from 'urql';
|
|
4
|
+
import {
|
|
5
|
+
InsertClientDocument,
|
|
6
|
+
type InsertClientMutation,
|
|
7
|
+
type InsertClientMutationVariables,
|
|
8
|
+
} from '../gql/graphql.js';
|
|
9
|
+
import { handleCommonErrors } from '../helpers/error-handling.js';
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
|
|
12
|
+
/* GraphQL */ `
|
|
13
|
+
mutation InsertClient($fields: ClientInsertInput!) {
|
|
14
|
+
insertClient(fields: $fields) {
|
|
15
|
+
__typename
|
|
16
|
+
... on Client {
|
|
17
|
+
id
|
|
18
|
+
}
|
|
19
|
+
... on CommonError {
|
|
20
|
+
message
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
type InsertClientSuccessfulResult = Extract<
|
|
27
|
+
InsertClientMutation['insertClient'],
|
|
28
|
+
{ __typename: 'Client' }
|
|
29
|
+
>;
|
|
30
|
+
|
|
31
|
+
type UseInsertClient = {
|
|
32
|
+
fetching: boolean;
|
|
33
|
+
insertClient: (
|
|
34
|
+
variables: InsertClientMutationVariables,
|
|
35
|
+
) => Promise<InsertClientSuccessfulResult | void>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const NOTIFICATION_ID = 'insertClient';
|
|
39
|
+
|
|
40
|
+
export const useInsertClient = (): UseInsertClient => {
|
|
41
|
+
// TODO: add authentication
|
|
42
|
+
// TODO: add local data update method after insert
|
|
43
|
+
|
|
44
|
+
const [{ fetching }, mutate] = useMutation(InsertClientDocument);
|
|
45
|
+
const insertClient = useCallback(
|
|
46
|
+
async (variables: InsertClientMutationVariables) => {
|
|
47
|
+
const errorMessage = `Error creating client [${variables.fields.businessId}]`;
|
|
48
|
+
const notificationId = `${NOTIFICATION_ID}-${variables.fields.businessId}`;
|
|
49
|
+
toast.loading('Creating Client...', {
|
|
50
|
+
id: notificationId,
|
|
51
|
+
});
|
|
52
|
+
try {
|
|
53
|
+
const res = await mutate(variables);
|
|
54
|
+
const data = handleCommonErrors(res, errorMessage, notificationId, 'insertClient');
|
|
55
|
+
if (data) {
|
|
56
|
+
toast.success('Success', {
|
|
57
|
+
id: notificationId,
|
|
58
|
+
description: `Client [${variables.fields.businessId}] was created`,
|
|
59
|
+
});
|
|
60
|
+
return data.insertClient;
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error(`${errorMessage}: ${e}`);
|
|
64
|
+
toast.error('Error', {
|
|
65
|
+
id: notificationId,
|
|
66
|
+
description: errorMessage,
|
|
67
|
+
duration: 100_000,
|
|
68
|
+
closeButton: true,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
return void 0;
|
|
72
|
+
},
|
|
73
|
+
[mutate],
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
fetching,
|
|
78
|
+
insertClient,
|
|
79
|
+
};
|
|
80
|
+
};
|
|
@@ -23,7 +23,7 @@ const NOTIFICATION_ID = 'mergeBusinesses';
|
|
|
23
23
|
|
|
24
24
|
export const useMergeBusinesses = (): UseMergeBusinesses => {
|
|
25
25
|
// TODO: add authentication
|
|
26
|
-
// TODO: add local data update method after
|
|
26
|
+
// TODO: add local data update method after change
|
|
27
27
|
|
|
28
28
|
const [{ fetching }, mutate] = useMutation(MergeBusinessesDocument);
|
|
29
29
|
const mergeBusinesses = useCallback(
|
|
@@ -47,7 +47,7 @@ const NOTIFICATION_ID = 'mergeCharges';
|
|
|
47
47
|
|
|
48
48
|
export const useMergeCharges = (): UseMergeCharges => {
|
|
49
49
|
// TODO: add authentication
|
|
50
|
-
// TODO: add local data update method after
|
|
50
|
+
// TODO: add local data update method after change
|
|
51
51
|
|
|
52
52
|
const [{ fetching }, mutate] = useMutation(MergeChargesDocument);
|
|
53
53
|
const mergeCharges = useCallback(
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
import { useMutation } from 'urql';
|
|
4
|
+
import {
|
|
5
|
+
UpdateClientDocument,
|
|
6
|
+
type UpdateClientMutation,
|
|
7
|
+
type UpdateClientMutationVariables,
|
|
8
|
+
} from '../gql/graphql.js';
|
|
9
|
+
import { handleCommonErrors } from '../helpers/error-handling.js';
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
|
|
12
|
+
/* GraphQL */ `
|
|
13
|
+
mutation UpdateClient($businessId: UUID!, $fields: ClientUpdateInput!) {
|
|
14
|
+
updateClient(businessId: $businessId, fields: $fields) {
|
|
15
|
+
__typename
|
|
16
|
+
... on Client {
|
|
17
|
+
id
|
|
18
|
+
}
|
|
19
|
+
... on CommonError {
|
|
20
|
+
message
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
type Client = Extract<UpdateClientMutation['updateClient'], { __typename: 'Client' }>;
|
|
27
|
+
|
|
28
|
+
type UseUpdateClient = {
|
|
29
|
+
fetching: boolean;
|
|
30
|
+
updateClient: (variables: UpdateClientMutationVariables) => Promise<Client | void>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const NOTIFICATION_ID = 'updateClient';
|
|
34
|
+
|
|
35
|
+
export const useUpdateClient = (): UseUpdateClient => {
|
|
36
|
+
// TODO: add authentication
|
|
37
|
+
// TODO: add local data update method after change
|
|
38
|
+
|
|
39
|
+
const [{ fetching }, mutate] = useMutation(UpdateClientDocument);
|
|
40
|
+
const updateClient = useCallback(
|
|
41
|
+
async (variables: UpdateClientMutationVariables) => {
|
|
42
|
+
const message = `Error updating client ID [${variables.businessId}]`;
|
|
43
|
+
const notificationId = `${NOTIFICATION_ID}-${variables.businessId}`;
|
|
44
|
+
toast.loading('Updating client', {
|
|
45
|
+
id: notificationId,
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
const res = await mutate(variables);
|
|
49
|
+
const data = handleCommonErrors(res, message, notificationId, 'updateClient');
|
|
50
|
+
if (data) {
|
|
51
|
+
toast.success('Success', {
|
|
52
|
+
id: notificationId,
|
|
53
|
+
description: 'Client updated',
|
|
54
|
+
});
|
|
55
|
+
return data.updateClient;
|
|
56
|
+
}
|
|
57
|
+
} catch (e) {
|
|
58
|
+
console.error(`${message}: ${e}`);
|
|
59
|
+
toast.error('Error', {
|
|
60
|
+
id: notificationId,
|
|
61
|
+
description: message,
|
|
62
|
+
duration: 100_000,
|
|
63
|
+
closeButton: true,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return void 0;
|
|
67
|
+
},
|
|
68
|
+
[mutate],
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
fetching,
|
|
73
|
+
updateClient,
|
|
74
|
+
};
|
|
75
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { toast } from 'sonner';
|
|
3
|
+
import { useMutation } from 'urql';
|
|
4
|
+
import {
|
|
5
|
+
UpdateContractDocument,
|
|
6
|
+
type UpdateContractMutation,
|
|
7
|
+
type UpdateContractMutationVariables,
|
|
8
|
+
} from '../gql/graphql.js';
|
|
9
|
+
import { handleCommonErrors } from '../helpers/error-handling.js';
|
|
10
|
+
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions -- used by codegen
|
|
12
|
+
/* GraphQL */ `
|
|
13
|
+
mutation UpdateContract($contractId: UUID!, $input: UpdateContractInput!) {
|
|
14
|
+
updateContract(contractId: $contractId, input: $input) {
|
|
15
|
+
id
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
|
|
20
|
+
type Contract = UpdateContractMutation['updateContract'];
|
|
21
|
+
|
|
22
|
+
type UseUpdateContract = {
|
|
23
|
+
updating: boolean;
|
|
24
|
+
updateContract: (variables: UpdateContractMutationVariables) => Promise<Contract | void>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const NOTIFICATION_ID = 'updateContract';
|
|
28
|
+
|
|
29
|
+
export const useUpdateContract = (): UseUpdateContract => {
|
|
30
|
+
// TODO: add authentication
|
|
31
|
+
// TODO: add local data update method after change
|
|
32
|
+
|
|
33
|
+
const [{ fetching }, mutate] = useMutation(UpdateContractDocument);
|
|
34
|
+
const updateContract = useCallback(
|
|
35
|
+
async (variables: UpdateContractMutationVariables) => {
|
|
36
|
+
const message = `Error updating contract ID [${variables.contractId}]`;
|
|
37
|
+
const notificationId = `${NOTIFICATION_ID}-${variables.contractId}`;
|
|
38
|
+
toast.loading('Updating contract', {
|
|
39
|
+
id: notificationId,
|
|
40
|
+
});
|
|
41
|
+
try {
|
|
42
|
+
const res = await mutate(variables);
|
|
43
|
+
const data = handleCommonErrors(res, message, notificationId, 'updateContract');
|
|
44
|
+
if (data) {
|
|
45
|
+
toast.success('Success', {
|
|
46
|
+
id: notificationId,
|
|
47
|
+
description: 'Contract updated',
|
|
48
|
+
});
|
|
49
|
+
return data.updateContract;
|
|
50
|
+
}
|
|
51
|
+
} catch (e) {
|
|
52
|
+
console.error(`${message}: ${e}`);
|
|
53
|
+
toast.error('Error', {
|
|
54
|
+
id: notificationId,
|
|
55
|
+
description: message,
|
|
56
|
+
duration: 100_000,
|
|
57
|
+
closeButton: true,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return void 0;
|
|
61
|
+
},
|
|
62
|
+
[mutate],
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
updating: fetching,
|
|
67
|
+
updateContract,
|
|
68
|
+
};
|
|
69
|
+
};
|
package/src/index.tsx
CHANGED
|
@@ -1,32 +1,14 @@
|
|
|
1
1
|
import { StrictMode } from 'react';
|
|
2
2
|
import { createRoot } from 'react-dom/client';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
createRoutesFromElements,
|
|
6
|
-
Route,
|
|
7
|
-
RouterProvider,
|
|
8
|
-
} from 'react-router-dom';
|
|
9
|
-
import { App } from './app.js';
|
|
10
|
-
import { Providers } from './providers/index.js';
|
|
3
|
+
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
|
|
4
|
+
import { routes } from './router/config.js';
|
|
11
5
|
import './index.css';
|
|
12
6
|
|
|
13
7
|
const rootElement = document.getElementById('root');
|
|
14
|
-
|
|
15
8
|
const root = createRoot(rootElement!);
|
|
16
9
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<Route
|
|
20
|
-
path="*"
|
|
21
|
-
element={
|
|
22
|
-
<Providers>
|
|
23
|
-
<App />
|
|
24
|
-
</Providers>
|
|
25
|
-
}
|
|
26
|
-
errorElement={<p>error</p>} // TODO: implement
|
|
27
|
-
/>,
|
|
28
|
-
),
|
|
29
|
-
);
|
|
10
|
+
// Create router with object-based configuration
|
|
11
|
+
const router = createBrowserRouter(routes);
|
|
30
12
|
|
|
31
13
|
root.render(
|
|
32
14
|
<StrictMode>
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { createContext,
|
|
2
|
-
import { Route, Routes, useNavigate } from 'react-router-dom';
|
|
3
|
-
import { NetworkError } from '@/components/screens/network-error.js';
|
|
4
|
-
import { LoginPage } from '../components/login-page.js';
|
|
1
|
+
import { createContext, useMemo, useState, type ReactNode } from 'react';
|
|
5
2
|
import { UserService } from '../services/user-service.js';
|
|
6
3
|
|
|
7
4
|
type ContextType = {
|
|
@@ -14,25 +11,19 @@ export const AuthContext = createContext<ContextType>({
|
|
|
14
11
|
setAuthService: () => void 0,
|
|
15
12
|
});
|
|
16
13
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
/**
|
|
15
|
+
* AuthProvider - provides authentication context
|
|
16
|
+
* Auth routing logic is now handled by route loaders
|
|
17
|
+
*/
|
|
18
|
+
export const AuthProvider = ({ children }: { children?: ReactNode }): ReactNode => {
|
|
19
|
+
// Use lazy initialization to prevent creating new UserService on every render
|
|
20
|
+
const [authService, setAuthService] = useState<UserService>(() => new UserService());
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
// Memoize the context value to prevent unnecessary re-renders
|
|
23
|
+
const contextValue = useMemo(() => ({ authService, setAuthService }), [authService]);
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
if (!loggedIn) {
|
|
25
|
-
navigate('/login');
|
|
26
|
-
}
|
|
27
|
-
}, [loggedIn, navigate]);
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<AuthContext.Provider value={{ authService, setAuthService }}>
|
|
31
|
-
<Routes>
|
|
32
|
-
<Route path="/login" element={<LoginPage />} />
|
|
33
|
-
<Route path="/network-error" element={<NetworkError />} />
|
|
34
|
-
<Route path="*" element={children} />
|
|
35
|
-
</Routes>
|
|
36
|
-
</AuthContext.Provider>
|
|
37
|
-
);
|
|
25
|
+
return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;
|
|
38
26
|
};
|
|
27
|
+
|
|
28
|
+
// Keep AuthGuard as alias for backwards compatibility
|
|
29
|
+
export const AuthGuard = AuthProvider;
|
package/src/providers/index.tsx
CHANGED
|
@@ -5,9 +5,14 @@ import { red } from '@mui/material/colors';
|
|
|
5
5
|
import { createTheme } from '@mui/material/styles';
|
|
6
6
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
7
7
|
import { Toaster } from '../components/ui/sonner.js';
|
|
8
|
-
import { AuthGuard } from './auth-guard.js';
|
|
8
|
+
import { AuthContext, AuthGuard, AuthProvider } from './auth-guard.js';
|
|
9
9
|
import { UrqlProvider } from './urql.js';
|
|
10
|
-
import { UserProvider } from './user-provider.js';
|
|
10
|
+
import { UserContext, UserProvider, type UserInfo } from './user-provider.js';
|
|
11
|
+
|
|
12
|
+
export { AuthGuard, AuthProvider, AuthContext };
|
|
13
|
+
export { FiltersContext } from './filters-context.js';
|
|
14
|
+
export { UrqlProvider };
|
|
15
|
+
export { UserProvider, UserContext, type UserInfo };
|
|
11
16
|
|
|
12
17
|
const queryClient = new QueryClient();
|
|
13
18
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createClient,
|
|
3
|
+
fetchExchange,
|
|
4
|
+
mapExchange,
|
|
5
|
+
type AnyVariables,
|
|
6
|
+
type Client,
|
|
7
|
+
type Operation,
|
|
8
|
+
} from 'urql';
|
|
9
|
+
import { authExchange } from '@urql/exchange-auth';
|
|
10
|
+
import { UserService } from '../services/user-service.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Singleton URQL client for use in loaders and server-side operations
|
|
14
|
+
* This is separate from the Provider client to avoid React context dependencies
|
|
15
|
+
*/
|
|
16
|
+
let globalClient: Client | null = null;
|
|
17
|
+
|
|
18
|
+
export function getUrqlClient(): Client {
|
|
19
|
+
if (globalClient) {
|
|
20
|
+
return globalClient;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const authService = new UserService();
|
|
24
|
+
|
|
25
|
+
let url: string;
|
|
26
|
+
switch (import.meta.env.MODE) {
|
|
27
|
+
case 'production': {
|
|
28
|
+
url = 'https://accounter.onrender.com/graphql';
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
case 'staging': {
|
|
32
|
+
url = 'https://accounter-staging.onrender.com/graphql';
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
default: {
|
|
36
|
+
url = 'http://localhost:4000/graphql';
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
globalClient = createClient({
|
|
42
|
+
url,
|
|
43
|
+
exchanges: [
|
|
44
|
+
mapExchange({
|
|
45
|
+
onResult(result) {
|
|
46
|
+
if (result.error?.networkError) {
|
|
47
|
+
console.error('Network Error:', result.error.networkError);
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
}),
|
|
51
|
+
authExchange(async utils => {
|
|
52
|
+
return {
|
|
53
|
+
addAuthToOperation(operation): Operation<void, AnyVariables> {
|
|
54
|
+
const token = authService.authToken();
|
|
55
|
+
if (!token) {
|
|
56
|
+
return operation;
|
|
57
|
+
}
|
|
58
|
+
return utils.appendHeaders(operation, {
|
|
59
|
+
Authorization: token,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
didAuthError(error, _operation): boolean {
|
|
63
|
+
return (
|
|
64
|
+
error?.response?.status === 401 ||
|
|
65
|
+
error?.graphQLErrors?.some(e => e.extensions?.code === 'FORBIDDEN')
|
|
66
|
+
);
|
|
67
|
+
},
|
|
68
|
+
async refreshAuth(): Promise<void> {
|
|
69
|
+
authService.logout();
|
|
70
|
+
// Redirect handled by route loader
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
}),
|
|
74
|
+
fetchExchange,
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
return globalClient;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Reset the global client (useful for tests or logout)
|
|
83
|
+
*/
|
|
84
|
+
export function resetUrqlClient(): void {
|
|
85
|
+
globalClient = null;
|
|
86
|
+
}
|