@powerhousedao/contributor-billing 0.0.89 → 0.0.90
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/dist/editors/contributor-billing/components/InvoiceTable/HeaderStats.d.ts.map +1 -1
- package/dist/editors/contributor-billing/components/InvoiceTable/HeaderStats.js +8 -4
- package/dist/editors/contributor-billing/util.d.ts +2 -1
- package/dist/editors/contributor-billing/util.d.ts.map +1 -1
- package/dist/editors/contributor-billing/util.js +45 -16
- package/dist/editors/invoice/ingestPDF.js +1 -1
- package/dist/editors/invoice/invoiceToGnosis.js +1 -1
- package/dist/editors/invoice/requestFinance.js +1 -1
- package/dist/editors/invoice/uploadPdfChunked.js +1 -1
- package/dist/migrate-zip.d.ts +2 -0
- package/dist/migrate-zip.d.ts.map +1 -0
- package/dist/migrate-zip.js +56 -0
- package/package.json +13 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HeaderStats.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/HeaderStats.tsx"],"names":[],"mappings":"AAkBA,eAAO,MAAM,WAAW,+
|
|
1
|
+
{"version":3,"file":"HeaderStats.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/HeaderStats.tsx"],"names":[],"mappings":"AAkBA,eAAO,MAAM,WAAW,+CAoHvB,CAAC"}
|
|
@@ -30,20 +30,24 @@ export const HeaderStats = () => {
|
|
|
30
30
|
for (const doc of invoices) {
|
|
31
31
|
const invoice = doc;
|
|
32
32
|
const invoiceAmount = invoice.state.global.totalPriceTaxIncl;
|
|
33
|
-
let invoiceCurrency = invoice.state.global.currency;
|
|
33
|
+
let invoiceCurrency = invoice.state.global.currency || 'USD'; // Fallback to USD if currency is empty
|
|
34
34
|
let selectCurrency = selectedCurrency;
|
|
35
35
|
if (invoiceCurrency === selectedCurrency) {
|
|
36
36
|
total += invoiceAmount;
|
|
37
37
|
}
|
|
38
38
|
else {
|
|
39
39
|
try {
|
|
40
|
+
// Only convert crypto currencies to USD for the API call
|
|
41
|
+
let fromCurrency = invoiceCurrency;
|
|
42
|
+
let toCurrency = selectedCurrency;
|
|
43
|
+
// Convert crypto to USD for API compatibility
|
|
40
44
|
if (invoiceCurrency === "DAI" || invoiceCurrency === "USDS") {
|
|
41
|
-
|
|
45
|
+
fromCurrency = "USD";
|
|
42
46
|
}
|
|
43
47
|
if (selectedCurrency === "DAI" || selectedCurrency === "USDS") {
|
|
44
|
-
|
|
48
|
+
toCurrency = "USD";
|
|
45
49
|
}
|
|
46
|
-
const exchangeRate = await getExchangeRate(
|
|
50
|
+
const exchangeRate = await getExchangeRate(fromCurrency, toCurrency, invoiceAmount);
|
|
47
51
|
total += invoiceAmount * exchangeRate;
|
|
48
52
|
}
|
|
49
53
|
catch (error) {
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Supports both fiat and crypto currencies.
|
|
4
4
|
* @param fromCurrency - The currency code to convert from (e.g., 'USD', 'DAI').
|
|
5
5
|
* @param toCurrency - The currency code to convert to (e.g., 'EUR', 'USDS').
|
|
6
|
+
* @param amount - The amount to convert (optional, used for validation).
|
|
6
7
|
* @returns The exchange rate from fromCurrency to toCurrency.
|
|
7
8
|
*/
|
|
8
|
-
export declare const getExchangeRate: (fromCurrency: string, toCurrency: string) => Promise<number>;
|
|
9
|
+
export declare const getExchangeRate: (fromCurrency: string, toCurrency: string, amount?: number) => Promise<number>;
|
|
9
10
|
//# sourceMappingURL=util.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../editors/contributor-billing/util.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../../../editors/contributor-billing/util.ts"],"names":[],"mappings":"AAYA;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,GAAU,cAAc,MAAM,EAAE,YAAY,MAAM,EAAE,SAAS,MAAM,KAAG,OAAO,CAAC,MAAM,CAoH/G,CAAC"}
|
|
@@ -1,35 +1,58 @@
|
|
|
1
1
|
// Cache for exchange rates to avoid repeated API calls
|
|
2
2
|
const exchangeRateCache = {};
|
|
3
|
+
/**
|
|
4
|
+
* Validates if an amount should trigger an exchange rate fetch
|
|
5
|
+
* @param amount - The amount to validate
|
|
6
|
+
* @returns true if the amount is valid and should trigger API calls
|
|
7
|
+
*/
|
|
8
|
+
const isValidAmount = (amount) => {
|
|
9
|
+
return amount !== undefined && amount !== null && !isNaN(amount) && amount > 0;
|
|
10
|
+
};
|
|
3
11
|
/**
|
|
4
12
|
* Fetches the exchange rate between two currencies using ExchangeRate-API.
|
|
5
13
|
* Supports both fiat and crypto currencies.
|
|
6
14
|
* @param fromCurrency - The currency code to convert from (e.g., 'USD', 'DAI').
|
|
7
15
|
* @param toCurrency - The currency code to convert to (e.g., 'EUR', 'USDS').
|
|
16
|
+
* @param amount - The amount to convert (optional, used for validation).
|
|
8
17
|
* @returns The exchange rate from fromCurrency to toCurrency.
|
|
9
18
|
*/
|
|
10
|
-
export const getExchangeRate = async (fromCurrency, toCurrency) => {
|
|
19
|
+
export const getExchangeRate = async (fromCurrency, toCurrency, amount) => {
|
|
20
|
+
// Normalize inputs
|
|
21
|
+
const base = (fromCurrency || '').trim().toUpperCase();
|
|
22
|
+
const quote = (toCurrency || '').trim().toUpperCase();
|
|
23
|
+
// Guard empty currencies
|
|
24
|
+
if (!base || !quote) {
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
11
27
|
// Return 1 if currencies are the same
|
|
12
|
-
if (
|
|
28
|
+
if (base === quote) {
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
// Skip API call if amount is explicitly provided and invalid
|
|
32
|
+
if (amount !== undefined && !isValidAmount(amount)) {
|
|
13
33
|
return 1;
|
|
14
34
|
}
|
|
15
35
|
// Create cache key
|
|
16
|
-
const cacheKey = `${
|
|
36
|
+
const cacheKey = `${base}_${quote}`;
|
|
17
37
|
// Return cached rate if available
|
|
18
38
|
if (exchangeRateCache[cacheKey] !== undefined) {
|
|
19
39
|
return exchangeRateCache[cacheKey];
|
|
20
40
|
}
|
|
21
41
|
try {
|
|
22
|
-
// Use
|
|
23
|
-
//
|
|
24
|
-
const
|
|
42
|
+
// Use a CORS-friendly endpoint that does not redirect
|
|
43
|
+
// API: https://open.er-api.com/v6/latest/{BASE}
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
|
46
|
+
const response = await fetch(`https://open.er-api.com/v6/latest/${base}`, { signal: controller.signal });
|
|
47
|
+
clearTimeout(timeoutId);
|
|
25
48
|
if (!response.ok) {
|
|
26
49
|
throw new Error(`Failed to fetch exchange rates: ${response.status}`);
|
|
27
50
|
}
|
|
28
51
|
const data = await response.json();
|
|
29
|
-
if (!data.rates || !data.rates[
|
|
30
|
-
throw new Error(`Exchange rate not found for ${
|
|
52
|
+
if (!data.rates || !data.rates[quote]) {
|
|
53
|
+
throw new Error(`Exchange rate not found for ${base} to ${quote}`);
|
|
31
54
|
}
|
|
32
|
-
const exchangeRate = data.rates[
|
|
55
|
+
const exchangeRate = data.rates[quote];
|
|
33
56
|
// Cache the result
|
|
34
57
|
exchangeRateCache[cacheKey] = exchangeRate;
|
|
35
58
|
return exchangeRate;
|
|
@@ -37,18 +60,21 @@ export const getExchangeRate = async (fromCurrency, toCurrency) => {
|
|
|
37
60
|
catch (error) {
|
|
38
61
|
console.error('ExchangeRate-API error:', error);
|
|
39
62
|
// Fallback: try a different approach for crypto currencies
|
|
40
|
-
if (['USDS', 'DAI'].includes(
|
|
63
|
+
if (['USDS', 'DAI'].includes(base) || ['USDS', 'DAI'].includes(quote)) {
|
|
41
64
|
try {
|
|
42
65
|
// For crypto currencies, use CoinGecko as fallback
|
|
43
66
|
const cryptoMapping = {
|
|
44
67
|
'USDS': 'usd-coin',
|
|
45
68
|
'DAI': 'dai',
|
|
46
69
|
};
|
|
47
|
-
const fromMapped = cryptoMapping[
|
|
48
|
-
const toMapped = cryptoMapping[
|
|
49
|
-
if (cryptoMapping[
|
|
70
|
+
const fromMapped = cryptoMapping[base] || base.toLowerCase();
|
|
71
|
+
const toMapped = cryptoMapping[quote] || quote.toLowerCase();
|
|
72
|
+
if (cryptoMapping[base]) {
|
|
50
73
|
// From crypto to fiat/crypto
|
|
51
|
-
const
|
|
74
|
+
const cryptoController = new AbortController();
|
|
75
|
+
const cryptoTimeoutId = setTimeout(() => cryptoController.abort(), 8000); // 8 second timeout
|
|
76
|
+
const response = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${fromMapped}&vs_currencies=${toMapped}`, { signal: cryptoController.signal });
|
|
77
|
+
clearTimeout(cryptoTimeoutId);
|
|
52
78
|
if (response.ok) {
|
|
53
79
|
const data = await response.json();
|
|
54
80
|
const rate = data[fromMapped]?.[toMapped];
|
|
@@ -58,9 +84,12 @@ export const getExchangeRate = async (fromCurrency, toCurrency) => {
|
|
|
58
84
|
}
|
|
59
85
|
}
|
|
60
86
|
}
|
|
61
|
-
else if (cryptoMapping[
|
|
87
|
+
else if (cryptoMapping[quote]) {
|
|
62
88
|
// From fiat to crypto
|
|
63
|
-
const
|
|
89
|
+
const cryptoController2 = new AbortController();
|
|
90
|
+
const cryptoTimeoutId2 = setTimeout(() => cryptoController2.abort(), 8000); // 8 second timeout
|
|
91
|
+
const response = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${toMapped}&vs_currencies=${fromMapped}`, { signal: cryptoController2.signal });
|
|
92
|
+
clearTimeout(cryptoTimeoutId2);
|
|
64
93
|
if (response.ok) {
|
|
65
94
|
const data = await response.json();
|
|
66
95
|
const rate = data[toMapped]?.[fromMapped];
|
|
@@ -6,7 +6,7 @@ import { uploadPdfChunked } from "./uploadPdfChunked.js";
|
|
|
6
6
|
import { getCountryCodeFromName } from "./utils/utils.js";
|
|
7
7
|
let GRAPHQL_URL = 'http://localhost:4001/graphql/invoice';
|
|
8
8
|
if (!window.document.baseURI.includes('localhost')) {
|
|
9
|
-
GRAPHQL_URL = 'https://switchboard.powerhouse.xyz/graphql/invoice';
|
|
9
|
+
GRAPHQL_URL = 'https://switchboard-dev.powerhouse.xyz/graphql/invoice';
|
|
10
10
|
}
|
|
11
11
|
export async function loadPDFFile({ file, dispatch, }) {
|
|
12
12
|
if (!file)
|
|
@@ -4,7 +4,7 @@ import { actions } from "../../document-models/invoice/index.js";
|
|
|
4
4
|
import { generateId } from "document-model";
|
|
5
5
|
let GRAPHQL_URL = "http://localhost:4001/graphql/invoice";
|
|
6
6
|
if (!window.document.baseURI.includes('localhost')) {
|
|
7
|
-
GRAPHQL_URL = 'https://switchboard.powerhouse.xyz/graphql/invoice';
|
|
7
|
+
GRAPHQL_URL = 'https://switchboard-dev.powerhouse.xyz/graphql/invoice';
|
|
8
8
|
}
|
|
9
9
|
const InvoiceToGnosis = ({ docState, dispatch, }) => {
|
|
10
10
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -4,7 +4,7 @@ import { actions } from "../../document-models/invoice/index.js";
|
|
|
4
4
|
import { generateId } from "document-model";
|
|
5
5
|
let GRAPHQL_URL = "http://localhost:4001/graphql/invoice";
|
|
6
6
|
if (!window.document.baseURI.includes('localhost')) {
|
|
7
|
-
GRAPHQL_URL = 'https://switchboard.powerhouse.xyz/graphql/invoice';
|
|
7
|
+
GRAPHQL_URL = 'https://switchboard-dev.powerhouse.xyz/graphql/invoice';
|
|
8
8
|
}
|
|
9
9
|
const RequestFinance = ({ docState, dispatch, }) => {
|
|
10
10
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
let GRAPHQL_URL = 'http://localhost:4001/graphql/invoice';
|
|
9
9
|
if (!window.document.baseURI.includes('localhost')) {
|
|
10
|
-
GRAPHQL_URL = 'https://switchboard.powerhouse.xyz/graphql/invoice';
|
|
10
|
+
GRAPHQL_URL = 'https://switchboard-dev.powerhouse.xyz/graphql/invoice';
|
|
11
11
|
}
|
|
12
12
|
export async function uploadPdfChunked(pdfData, endpoint = GRAPHQL_URL, chunkSize = 500 * 1024, // 500KB chunks
|
|
13
13
|
onProgress) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate-zip.d.ts","sourceRoot":"","sources":["../migrate-zip.js"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import JSZip from "jszip";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { documentModelDocumentModelModule } from "document-model";
|
|
5
|
+
import { argv } from "node:process";
|
|
6
|
+
const docModelMap = {
|
|
7
|
+
"powerhouse/document-model": documentModelDocumentModelModule,
|
|
8
|
+
// add other document models here
|
|
9
|
+
};
|
|
10
|
+
async function loadFromZip(zip, entryName) {
|
|
11
|
+
const entry = await zip.file(entryName).async("string");
|
|
12
|
+
return JSON.parse(entry);
|
|
13
|
+
}
|
|
14
|
+
async function resolvePath(argPath) {
|
|
15
|
+
if (fs.lstatSync(argPath).isDirectory()) {
|
|
16
|
+
const children = fs.readdirSync(argPath);
|
|
17
|
+
return children
|
|
18
|
+
.filter((child) => child.endsWith(".zip") &&
|
|
19
|
+
fs.lstatSync(path.join(argPath, child)).isFile())
|
|
20
|
+
.map((child) => path.join(argPath, child));
|
|
21
|
+
}
|
|
22
|
+
return [path.resolve(argPath)];
|
|
23
|
+
}
|
|
24
|
+
async function migrateZip(zipPath) {
|
|
25
|
+
const file = fs.readFileSync(zipPath);
|
|
26
|
+
const docZip = new JSZip();
|
|
27
|
+
await docZip.loadAsync(file);
|
|
28
|
+
const header = await loadFromZip(docZip, "header.json");
|
|
29
|
+
const operations = await loadFromZip(docZip, "operations.json");
|
|
30
|
+
const documentModel = docModelMap[header.documentType];
|
|
31
|
+
if (!documentModel) {
|
|
32
|
+
throw new Error(`Unknown document type: ${header.documentType}. Add it to docModelMap.`);
|
|
33
|
+
}
|
|
34
|
+
const result = Object.values(operations)
|
|
35
|
+
.flat()
|
|
36
|
+
.reduce((doc, operation) => {
|
|
37
|
+
const action = "action" in operation ? operation.action : operation;
|
|
38
|
+
return documentModel.reducer(doc, action);
|
|
39
|
+
}, documentModel.utils.createDocument());
|
|
40
|
+
const targetName = path.basename(zipPath, path.extname(zipPath));
|
|
41
|
+
const targetPath = path.resolve(zipPath, "../");
|
|
42
|
+
await documentModel.utils.saveToFile(result, targetPath, `${targetName}-migrated`);
|
|
43
|
+
console.info(`Migrated ${path.relative(process.cwd(), zipPath)} to ${path.relative(process.cwd(), path.join(targetPath, `${targetName}-migrated.${documentModel.documentModel.extension}.zip`))}`);
|
|
44
|
+
}
|
|
45
|
+
const paths = argv.length > 2 ? argv.slice(2) : [process.cwd()];
|
|
46
|
+
paths.forEach(async (val) => {
|
|
47
|
+
const resolvedPaths = await resolvePath(val);
|
|
48
|
+
for (const zipPath of resolvedPaths) {
|
|
49
|
+
try {
|
|
50
|
+
await migrateZip(zipPath);
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
console.error(`Error migrating ${zipPath}`, e);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/contributor-billing",
|
|
3
3
|
"description": "Document models that help contributors of open organisations get paid anonymously for their work on a monthly basis.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.90",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -57,9 +57,9 @@
|
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@google-cloud/documentai": "^8.12.0",
|
|
60
|
-
"@powerhousedao/builder-tools": "^5.0.0-staging.
|
|
61
|
-
"@powerhousedao/common": "^5.0.0-staging.
|
|
62
|
-
"@powerhousedao/design-system": "^5.0.0-staging.
|
|
60
|
+
"@powerhousedao/builder-tools": "^5.0.0-staging.15",
|
|
61
|
+
"@powerhousedao/common": "^5.0.0-staging.15",
|
|
62
|
+
"@powerhousedao/design-system": "^5.0.0-staging.15",
|
|
63
63
|
"@powerhousedao/document-engineering": "^1.37.0",
|
|
64
64
|
"@react-pdf/renderer": "^4.3.0",
|
|
65
65
|
"@safe-global/api-kit": "^3.0.1",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@types/cors": "^2.8.17",
|
|
70
70
|
"axios": "^1.9.0",
|
|
71
71
|
"cors": "^2.8.5",
|
|
72
|
-
"document-model": "^5.0.0-staging.
|
|
72
|
+
"document-model": "^5.0.0-staging.15",
|
|
73
73
|
"dotenv": "^16.5.0",
|
|
74
74
|
"error": "^10.4.0",
|
|
75
75
|
"ethers": "^6.14.0",
|
|
@@ -85,23 +85,24 @@
|
|
|
85
85
|
"@electric-sql/pglite": "^0.2.12",
|
|
86
86
|
"@eslint/js": "^9.25.0",
|
|
87
87
|
"@powerhousedao/analytics-engine-core": "^0.5.0",
|
|
88
|
-
"@powerhousedao/codegen": "^5.0.0-staging.
|
|
89
|
-
"@powerhousedao/ph-cli": "^5.0.0-staging.
|
|
90
|
-
"@powerhousedao/reactor-api": "^5.0.0-staging.
|
|
91
|
-
"@powerhousedao/reactor-browser": "^5.0.0-staging.
|
|
92
|
-
"@powerhousedao/reactor-local": "^5.0.0-staging.
|
|
88
|
+
"@powerhousedao/codegen": "^5.0.0-staging.15",
|
|
89
|
+
"@powerhousedao/ph-cli": "^5.0.0-staging.15",
|
|
90
|
+
"@powerhousedao/reactor-api": "^5.0.0-staging.15",
|
|
91
|
+
"@powerhousedao/reactor-browser": "^5.0.0-staging.15",
|
|
92
|
+
"@powerhousedao/reactor-local": "^5.0.0-staging.15",
|
|
93
93
|
"@powerhousedao/scalars": "^1.33.1-staging.5",
|
|
94
|
-
"@powerhousedao/switchboard": "^5.0.0-staging.
|
|
94
|
+
"@powerhousedao/switchboard": "^5.0.0-staging.15",
|
|
95
95
|
"@tailwindcss/cli": "^4.1.4",
|
|
96
96
|
"@testing-library/react": "^16.3.0",
|
|
97
97
|
"@types/node": "^22.14.1",
|
|
98
98
|
"@types/react": "^18.3.20",
|
|
99
99
|
"@vitejs/plugin-react": "^4.4.1",
|
|
100
|
-
"document-drive": "^5.0.0-staging.
|
|
100
|
+
"document-drive": "^5.0.0-staging.15",
|
|
101
101
|
"eslint": "^9.25.0",
|
|
102
102
|
"eslint-plugin-react": "^7.37.5",
|
|
103
103
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
104
104
|
"globals": "^16.0.0",
|
|
105
|
+
"jszip": "^3.10.1",
|
|
105
106
|
"package-manager-detector": "^0.2.8",
|
|
106
107
|
"pm2": "^5.4.3",
|
|
107
108
|
"react": "^18.3.1",
|