@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.
@@ -1 +1 @@
1
- {"version":3,"file":"HeaderStats.d.ts","sourceRoot":"","sources":["../../../../../editors/contributor-billing/components/InvoiceTable/HeaderStats.tsx"],"names":[],"mappings":"AAkBA,eAAO,MAAM,WAAW,+CA6GvB,CAAC"}
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
- invoiceCurrency = "USD";
45
+ fromCurrency = "USD";
42
46
  }
43
47
  if (selectedCurrency === "DAI" || selectedCurrency === "USDS") {
44
- selectCurrency = "USD";
48
+ toCurrency = "USD";
45
49
  }
46
- const exchangeRate = await getExchangeRate(invoiceCurrency, selectCurrency);
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":"AAGA;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAU,cAAc,MAAM,EAAE,YAAY,MAAM,KAAG,OAAO,CAAC,MAAM,CAiF9F,CAAC"}
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 (fromCurrency === toCurrency) {
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 = `${fromCurrency}_${toCurrency}`;
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 ExchangeRate-API.com for currency conversion
23
- // This API supports both fiat and crypto currencies
24
- const response = await fetch(`https://api.exchangerate-api.com/v4/latest/${fromCurrency}`);
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[toCurrency]) {
30
- throw new Error(`Exchange rate not found for ${fromCurrency} to ${toCurrency}`);
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[toCurrency];
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(fromCurrency) || ['USDS', 'DAI'].includes(toCurrency)) {
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[fromCurrency] || fromCurrency.toLowerCase();
48
- const toMapped = cryptoMapping[toCurrency] || toCurrency.toLowerCase();
49
- if (cryptoMapping[fromCurrency]) {
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 response = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${fromMapped}&vs_currencies=${toMapped}`);
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[toCurrency]) {
87
+ else if (cryptoMapping[quote]) {
62
88
  // From fiat to crypto
63
- const response = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${toMapped}&vs_currencies=${fromMapped}`);
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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=migrate-zip.d.ts.map
@@ -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.89",
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.14",
61
- "@powerhousedao/common": "^5.0.0-staging.14",
62
- "@powerhousedao/design-system": "^5.0.0-staging.14",
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.14",
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.14",
89
- "@powerhousedao/ph-cli": "^5.0.0-staging.14",
90
- "@powerhousedao/reactor-api": "^5.0.0-staging.14",
91
- "@powerhousedao/reactor-browser": "^5.0.0-staging.14",
92
- "@powerhousedao/reactor-local": "^5.0.0-staging.14",
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.14",
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.14",
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",