diff --git a/package-lock.json b/package-lock.json
index 0e3cd024..b4dfbd9d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,13 +16,19 @@
"@codemirror/search": "^6.5.11",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.1",
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@uiw/react-codemirror": "^4.25.1",
"codemirror": "^6.0.2",
"diff-match-patch": "^1.0.5",
+ "html2pdf.js": "^0.12.1",
"js-beautify": "^1.15.4",
+ "jspdf": "^3.0.3",
+ "jspdf-autotable": "^5.0.2",
"lucide-react": "^0.540.0",
"papaparse": "^5.5.3",
"react": "18.3.1",
@@ -2372,6 +2378,59 @@
"postcss-selector-parser": "^6.0.10"
}
},
+ "node_modules/@dnd-kit/accessibility": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz",
+ "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/core": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz",
+ "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/accessibility": "^3.1.1",
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/sortable": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz",
+ "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==",
+ "license": "MIT",
+ "dependencies": {
+ "@dnd-kit/utilities": "^3.2.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "@dnd-kit/core": "^6.3.0",
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@dnd-kit/utilities": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz",
+ "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.7.0",
"dev": true,
@@ -4092,6 +4151,12 @@
"@types/node": "*"
}
},
+ "node_modules/@types/pako": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz",
+ "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==",
+ "license": "MIT"
+ },
"node_modules/@types/parse-json": {
"version": "4.0.2",
"dev": true,
@@ -4112,6 +4177,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/raf": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
+ "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
+ "license": "MIT",
+ "optional": true
+ },
"node_modules/@types/range-parser": {
"version": "1.2.7",
"dev": true,
@@ -4177,7 +4249,7 @@
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/@types/ws": {
@@ -5416,6 +5488,15 @@
"version": "1.0.2",
"license": "MIT"
},
+ "node_modules/base64-arraybuffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
+ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6.0"
+ }
+ },
"node_modules/batch": {
"version": "0.6.1",
"dev": true,
@@ -5717,6 +5798,26 @@
],
"license": "CC-BY-4.0"
},
+ "node_modules/canvg": {
+ "version": "3.0.11",
+ "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz",
+ "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "@types/raf": "^3.4.0",
+ "core-js": "^3.8.3",
+ "raf": "^3.4.1",
+ "regenerator-runtime": "^0.13.7",
+ "rgbcolor": "^1.0.1",
+ "stackblur-canvas": "^2.0.0",
+ "svg-pathdata": "^6.0.3"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/case-sensitive-paths-webpack-plugin": {
"version": "2.4.0",
"dev": true,
@@ -6191,7 +6292,7 @@
},
"node_modules/core-js": {
"version": "3.45.1",
- "dev": true,
+ "devOptional": true,
"hasInstallScript": true,
"license": "MIT",
"funding": {
@@ -6310,6 +6411,15 @@
"postcss": "^8.4"
}
},
+ "node_modules/css-line-break": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
+ "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
+ "license": "MIT",
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/css-loader": {
"version": "6.11.0",
"dev": true,
@@ -7074,6 +7184,16 @@
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
+ "node_modules/dompurify": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
+ "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optional": true,
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
"node_modules/domutils": {
"version": "2.8.0",
"dev": true,
@@ -8291,6 +8411,17 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-png": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz",
+ "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/pako": "^2.0.3",
+ "iobuffer": "^5.3.2",
+ "pako": "^2.1.0"
+ }
+ },
"node_modules/fast-uri": {
"version": "3.0.6",
"dev": true,
@@ -8333,6 +8464,12 @@
"bser": "2.1.1"
}
},
+ "node_modules/fflate": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
+ "license": "MIT"
+ },
"node_modules/file-entry-cache": {
"version": "6.0.1",
"dev": true,
@@ -9287,6 +9424,29 @@
}
}
},
+ "node_modules/html2canvas": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
+ "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
+ "license": "MIT",
+ "dependencies": {
+ "css-line-break": "^2.1.0",
+ "text-segmentation": "^1.0.3"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/html2pdf.js": {
+ "version": "0.12.1",
+ "resolved": "https://registry.npmjs.org/html2pdf.js/-/html2pdf.js-0.12.1.tgz",
+ "integrity": "sha512-3rBWQ96H5oOU9jtoz3MnE/epGi27ig9h8aonBk4JTpvUERM3lMRxhIRckhJZEi4wE0YfRINoYOIDY0hLY0CHgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "html2canvas": "^1.0.0",
+ "jspdf": "^3.0.0"
+ }
+ },
"node_modules/htmlparser2": {
"version": "6.1.0",
"dev": true,
@@ -9541,6 +9701,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/iobuffer": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz",
+ "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==",
+ "license": "MIT"
+ },
"node_modules/ipaddr.js": {
"version": "2.2.0",
"dev": true,
@@ -11229,6 +11395,32 @@
"node": ">=0.10.0"
}
},
+ "node_modules/jspdf": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz",
+ "integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.9",
+ "fast-png": "^6.2.0",
+ "fflate": "^0.8.1"
+ },
+ "optionalDependencies": {
+ "canvg": "^3.0.11",
+ "core-js": "^3.6.0",
+ "dompurify": "^3.2.4",
+ "html2canvas": "^1.0.0-rc.5"
+ }
+ },
+ "node_modules/jspdf-autotable": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-5.0.2.tgz",
+ "integrity": "sha512-YNKeB7qmx3pxOLcNeoqAv3qTS7KuvVwkFe5AduCawpop3NOkBUtqDToxNc225MlNecxT4kP2Zy3z/y/yvGdXUQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "jspdf": "^2 || ^3"
+ }
+ },
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"dev": true,
@@ -12089,6 +12281,12 @@
"version": "1.0.1",
"license": "BlueOak-1.0.0"
},
+ "node_modules/pako": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
+ "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/papaparse": {
"version": "5.5.3",
"license": "MIT"
@@ -12209,7 +12407,7 @@
},
"node_modules/performance-now": {
"version": "2.1.0",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/picocolors": {
@@ -13716,7 +13914,7 @@
},
"node_modules/raf": {
"version": "3.4.1",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"performance-now": "^2.1.0"
@@ -14172,7 +14370,7 @@
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/regex-parser": {
@@ -14418,6 +14616,16 @@
"node": ">=0.10.0"
}
},
+ "node_modules/rgbcolor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
+ "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
+ "license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
+ "optional": true,
+ "engines": {
+ "node": ">= 0.8.15"
+ }
+ },
"node_modules/rimraf": {
"version": "3.0.2",
"dev": true,
@@ -15191,6 +15399,16 @@
"node": ">=8"
}
},
+ "node_modules/stackblur-canvas": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
+ "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.14"
+ }
+ },
"node_modules/stackframe": {
"version": "1.3.4",
"dev": true,
@@ -15681,6 +15899,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/svg-pathdata": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
+ "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/svgo": {
"version": "1.3.2",
"dev": true,
@@ -16042,6 +16270,15 @@
"node": "*"
}
},
+ "node_modules/text-segmentation": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
+ "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
+ "license": "MIT",
+ "dependencies": {
+ "utrie": "^1.0.2"
+ }
+ },
"node_modules/text-table": {
"version": "0.2.0",
"dev": true,
@@ -16175,7 +16412,6 @@
},
"node_modules/tslib": {
"version": "2.8.1",
- "dev": true,
"license": "0BSD"
},
"node_modules/tsutils": {
@@ -16523,6 +16759,15 @@
"node": ">= 0.4.0"
}
},
+ "node_modules/utrie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
+ "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
+ "license": "MIT",
+ "dependencies": {
+ "base64-arraybuffer": "^1.0.2"
+ }
+ },
"node_modules/uuid": {
"version": "8.3.2",
"dev": true,
diff --git a/package.json b/package.json
index 5bbb3f42..aff8dfd5 100644
--- a/package.json
+++ b/package.json
@@ -12,13 +12,19 @@
"@codemirror/search": "^6.5.11",
"@codemirror/theme-one-dark": "^6.1.3",
"@codemirror/view": "^6.38.1",
+ "@dnd-kit/core": "^6.3.1",
+ "@dnd-kit/sortable": "^10.0.0",
+ "@dnd-kit/utilities": "^3.2.2",
"@testing-library/jest-dom": "^6.8.0",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@uiw/react-codemirror": "^4.25.1",
"codemirror": "^6.0.2",
"diff-match-patch": "^1.0.5",
+ "html2pdf.js": "^0.12.1",
"js-beautify": "^1.15.4",
+ "jspdf": "^3.0.3",
+ "jspdf-autotable": "^5.0.2",
"lucide-react": "^0.540.0",
"papaparse": "^5.5.3",
"react": "18.3.1",
diff --git a/public/utils/currencies.json b/public/utils/currencies.json
new file mode 100644
index 00000000..22049ad6
--- /dev/null
+++ b/public/utils/currencies.json
@@ -0,0 +1 @@
+[ { "code":"AED", "name":"United Arab Emirates dirham", "symbol":"" }, { "code":"AFN", "name":"Afghan afghani", "symbol":"" }, { "code":"ALL", "name":"Albanian lek", "symbol":"" }, { "code":"AMD", "name":"Armenian dram", "symbol":"" }, { "code":"ANG", "name":"Netherlands Antillean guilder", "symbol":"" }, { "code":"AOA", "name":"Angolan kwanza", "symbol":"" }, { "code":"ARS", "name":"Argentine peso", "symbol":"" }, { "code":"AUD", "name":"Australian dollar", "symbol":"" }, { "code":"AWG", "name":"Aruban florin", "symbol":"" }, { "code":"AZN", "name":"Azerbaijani manat", "symbol":"" }, { "code":"BAM", "name":"Bosnia and Herzegovina convertible mark", "symbol":"" }, { "code":"BBD", "name":"Barbados dollar", "symbol":"" }, { "code":"BDT", "name":"Bangladeshi taka", "symbol":"" }, { "code":"BGN", "name":"Bulgarian lev", "symbol":"" }, { "code":"BHD", "name":"Bahraini dinar", "symbol":"" }, { "code":"BIF", "name":"Burundian franc", "symbol":"" }, { "code":"BMD", "name":"Bermudian dollar", "symbol":"" }, { "code":"BND", "name":"Brunei dollar", "symbol":"" }, { "code":"BOB", "name":"Boliviano", "symbol":"" }, { "code":"BRL", "name":"Brazilian real", "symbol":"" }, { "code":"BSD", "name":"Bahamian dollar", "symbol":"" }, { "code":"BTN", "name":"Bhutanese ngultrum", "symbol":"" }, { "code":"BWP", "name":"Botswana pula", "symbol":"" }, { "code":"BYN", "name":"New Belarusian ruble", "symbol":"" }, { "code":"BYR", "name":"Belarusian ruble", "symbol":"" }, { "code":"BZD", "name":"Belize dollar", "symbol":"" }, { "code":"CAD", "name":"Canadian dollar", "symbol":"" }, { "code":"CDF", "name":"Congolese franc", "symbol":"" }, { "code":"CHF", "name":"Swiss franc", "symbol":"" }, { "code":"CLF", "name":"Unidad de Fomento", "symbol":"" }, { "code":"CLP", "name":"Chilean peso", "symbol":"" }, { "code":"CNY", "name":"Renminbi|Chinese yuan", "symbol":"" }, { "code":"COP", "name":"Colombian peso", "symbol":"" }, { "code":"CRC", "name":"Costa Rican colon", "symbol":"₡" }, { "code":"CUC", "name":"Cuban convertible peso", "symbol":"" }, { "code":"CUP", "name":"Cuban peso", "symbol":"" }, { "code":"CVE", "name":"Cape Verde escudo", "symbol":"" }, { "code":"CZK", "name":"Czech koruna", "symbol":"" }, { "code":"DJF", "name":"Djiboutian franc", "symbol":"" }, { "code":"DKK", "name":"Danish krone", "symbol":"" }, { "code":"DOP", "name":"Dominican peso", "symbol":"" }, { "code":"DZD", "name":"Algerian dinar", "symbol":"" }, { "code":"EGP", "name":"Egyptian pound", "symbol":"" }, { "code":"ERN", "name":"Eritrean nakfa", "symbol":"" }, { "code":"ETB", "name":"Ethiopian birr", "symbol":"" }, { "code":"EUR", "name":"Euro", "symbol":"€" }, { "code":"FJD", "name":"Fiji dollar", "symbol":"" }, { "code":"FKP", "name":"Falkland Islands pound", "symbol":"" }, { "code":"GBP", "name":"Pound sterling", "symbol":"£" }, { "code":"GEL", "name":"Georgian lari", "symbol":"" }, { "code":"GHS", "name":"Ghanaian cedi", "symbol":"" }, { "code":"GIP", "name":"Gibraltar pound", "symbol":"" }, { "code":"GMD", "name":"Gambian dalasi", "symbol":"" }, { "code":"GNF", "name":"Guinean franc", "symbol":"" }, { "code":"GTQ", "name":"Guatemalan quetzal", "symbol":"" }, { "code":"GYD", "name":"Guyanese dollar", "symbol":"" }, { "code":"HKD", "name":"Hong Kong dollar", "symbol":"" }, { "code":"HNL", "name":"Honduran lempira", "symbol":"" }, { "code":"HRK", "name":"Croatian kuna", "symbol":"" }, { "code":"HTG", "name":"Haitian gourde", "symbol":"" }, { "code":"HUF", "name":"Hungarian forint", "symbol":"" }, { "code":"IDR", "name":"Indonesian rupiah", "symbol":"Rp" }, { "code":"ILS", "name":"Israeli new shekel", "symbol":"₪" }, { "code":"INR", "name":"Indian rupee", "symbol":"₹" }, { "code":"IQD", "name":"Iraqi dinar", "symbol":"" }, { "code":"IRR", "name":"Iranian rial", "symbol":"" }, { "code":"ISK", "name":"Icelandic króna", "symbol":"" }, { "code":"JMD", "name":"Jamaican dollar", "symbol":"" }, { "code":"JOD", "name":"Jordanian dinar", "symbol":"" }, { "code":"JPY", "name":"Japanese yen", "symbol":"¥" }, { "code":"KES", "name":"Kenyan shilling", "symbol":"" }, { "code":"KGS", "name":"Kyrgyzstani som", "symbol":"" }, { "code":"KHR", "name":"Cambodian riel", "symbol":"" }, { "code":"KMF", "name":"Comoro franc", "symbol":"" }, { "code":"KPW", "name":"North Korean won", "symbol":"" }, { "code":"KRW", "name":"South Korean won", "symbol":"₩" }, { "code":"KWD", "name":"Kuwaiti dinar", "symbol":"" }, { "code":"KYD", "name":"Cayman Islands dollar", "symbol":"" }, { "code":"KZT", "name":"Kazakhstani tenge", "symbol":"" }, { "code":"LAK", "name":"Lao kip", "symbol":"" }, { "code":"LBP", "name":"Lebanese pound", "symbol":"" }, { "code":"LKR", "name":"Sri Lankan rupee", "symbol":"" }, { "code":"LRD", "name":"Liberian dollar", "symbol":"" }, { "code":"LSL", "name":"Lesotho loti", "symbol":"" }, { "code":"LYD", "name":"Libyan dinar", "symbol":"" }, { "code":"MAD", "name":"Moroccan dirham", "symbol":"" }, { "code":"MDL", "name":"Moldovan leu", "symbol":"" }, { "code":"MGA", "name":"Malagasy ariary", "symbol":"" }, { "code":"MKD", "name":"Macedonian denar", "symbol":"" }, { "code":"MMK", "name":"Myanmar kyat", "symbol":"" }, { "code":"MNT", "name":"Mongolian tögrög", "symbol":"" }, { "code":"MOP", "name":"Macanese pataca", "symbol":"" }, { "code":"MRO", "name":"Mauritanian ouguiya", "symbol":"" }, { "code":"MUR", "name":"Mauritian rupee", "symbol":"" }, { "code":"MVR", "name":"Maldivian rufiyaa", "symbol":"" }, { "code":"MWK", "name":"Malawian kwacha", "symbol":"" }, { "code":"MXN", "name":"Mexican peso", "symbol":"" }, { "code":"MXV", "name":"Mexican Unidad de Inversion", "symbol":"" }, { "code":"MYR", "name":"Malaysian ringgit", "symbol":"RM" }, { "code":"MZN", "name":"Mozambican metical", "symbol":"" }, { "code":"NAD", "name":"Namibian dollar", "symbol":"" }, { "code":"NGN", "name":"Nigerian naira", "symbol":"₦" }, { "code":"NIO", "name":"Nicaraguan córdoba", "symbol":"" }, { "code":"NOK", "name":"Norwegian krone", "symbol":"" }, { "code":"NPR", "name":"Nepalese rupee", "symbol":"" }, { "code":"NZD", "name":"New Zealand dollar", "symbol":"" }, { "code":"OMR", "name":"Omani rial", "symbol":"" }, { "code":"PAB", "name":"Panamanian balboa", "symbol":"" }, { "code":"PEN", "name":"Peruvian Sol", "symbol":"" }, { "code":"PGK", "name":"Papua New Guinean kina", "symbol":"" }, { "code":"PHP", "name":"Philippine peso", "symbol":"₱" }, { "code":"PKR", "name":"Pakistani rupee", "symbol":"" }, { "code":"PLN", "name":"Polish złoty", "symbol":"zł" }, { "code":"PYG", "name":"Paraguayan guaraní", "symbol":"₲" }, { "code":"QAR", "name":"Qatari riyal", "symbol":"" }, { "code":"RON", "name":"Romanian leu", "symbol":"" }, { "code":"RSD", "name":"Serbian dinar", "symbol":"" }, { "code":"RUB", "name":"Russian ruble", "symbol":"" }, { "code":"RWF", "name":"Rwandan franc", "symbol":"" }, { "code":"SAR", "name":"Saudi riyal", "symbol":"" }, { "code":"SBD", "name":"Solomon Islands dollar", "symbol":"" }, { "code":"SCR", "name":"Seychelles rupee", "symbol":"" }, { "code":"SDG", "name":"Sudanese pound", "symbol":"" }, { "code":"SEK", "name":"Swedish krona", "symbol":"" }, { "code":"SGD", "name":"Singapore dollar", "symbol":"" }, { "code":"SHP", "name":"Saint Helena pound", "symbol":"" }, { "code":"SLL", "name":"Sierra Leonean leone", "symbol":"" }, { "code":"SOS", "name":"Somali shilling", "symbol":"" }, { "code":"SRD", "name":"Surinamese dollar", "symbol":"" }, { "code":"SSP", "name":"South Sudanese pound", "symbol":"" }, { "code":"STD", "name":"São Tomé and Príncipe dobra", "symbol":"" }, { "code":"SVC", "name":"Salvadoran colón", "symbol":"" }, { "code":"SYP", "name":"Syrian pound", "symbol":"" }, { "code":"SZL", "name":"Swazi lilangeni", "symbol":"" }, { "code":"THB", "name":"Thai baht", "symbol":"฿" }, { "code":"TJS", "name":"Tajikistani somoni", "symbol":"" }, { "code":"TMT", "name":"Turkmenistani manat", "symbol":"" }, { "code":"TND", "name":"Tunisian dinar", "symbol":"" }, { "code":"TOP", "name":"Tongan paʻanga", "symbol":"" }, { "code":"TRY", "name":"Turkish lira", "symbol":"" }, { "code":"TTD", "name":"Trinidad and Tobago dollar", "symbol":"" }, { "code":"TWD", "name":"New Taiwan dollar", "symbol":"" }, { "code":"TZS", "name":"Tanzanian shilling", "symbol":"" }, { "code":"UAH", "name":"Ukrainian hryvnia", "symbol":"₴" }, { "code":"UGX", "name":"Ugandan shilling", "symbol":"" }, { "code":"USD", "name":"United States dollar", "symbol":"$" }, { "code":"UYI", "name":"Uruguay Peso en Unidades Indexadas", "symbol":"" }, { "code":"UYU", "name":"Uruguayan peso", "symbol":"" }, { "code":"UZS", "name":"Uzbekistan som", "symbol":"" }, { "code":"VEF", "name":"Venezuelan bolívar", "symbol":"" }, { "code":"VND", "name":"Vietnamese đồng", "symbol":"₫" }, { "code":"VUV", "name":"Vanuatu vatu", "symbol":"" }, { "code":"WST", "name":"Samoan tala", "symbol":"" }, { "code":"XAF", "name":"Central African CFA franc", "symbol":"" }, { "code":"XCD", "name":"East Caribbean dollar", "symbol":"" }, { "code":"XOF", "name":"West African CFA franc", "symbol":"" }, { "code":"XPF", "name":"CFP franc", "symbol":"" }, { "code":"XXX", "name":"No currency", "symbol":"" }, { "code":"YER", "name":"Yemeni rial", "symbol":"" }, { "code":"ZAR", "name":"South African rand", "symbol":"" }, { "code":"ZMW", "name":"Zambian kwacha", "symbol":"" }, { "code":"ZWL", "name":"Zimbabwean dollar", "symbol":"" } ]
\ No newline at end of file
diff --git a/src/App.js b/src/App.js
index b9cb1133..d605eb62 100644
--- a/src/App.js
+++ b/src/App.js
@@ -13,6 +13,9 @@ import DiffTool from './pages/DiffTool';
import TextLengthTool from './pages/TextLengthTool';
import ObjectEditor from './pages/ObjectEditor';
import TableEditor from './pages/TableEditor';
+import InvoiceEditor from './pages/InvoiceEditor';
+import InvoicePreview from './pages/InvoicePreview';
+import InvoicePreviewMinimal from './pages/InvoicePreviewMinimal';
import ReleaseNotes from './pages/ReleaseNotes';
import TermsOfService from './pages/TermsOfService';
import PrivacyPolicy from './pages/PrivacyPolicy';
@@ -42,6 +45,9 @@ function App() {
} />
} />
} />
+ } />
+ } />
+ } />
} />
} />
} />
diff --git a/src/components/CodeEditor.js b/src/components/CodeEditor.js
new file mode 100644
index 00000000..3b400b74
--- /dev/null
+++ b/src/components/CodeEditor.js
@@ -0,0 +1,99 @@
+import React from 'react';
+import CodeMirror from '@uiw/react-codemirror';
+import { javascript } from '@codemirror/lang-javascript';
+import { json } from '@codemirror/lang-json';
+import { html } from '@codemirror/lang-html';
+import { css } from '@codemirror/lang-css';
+import { oneDark } from '@codemirror/theme-one-dark';
+import { EditorView } from '@codemirror/view';
+
+const CodeEditor = ({
+ value,
+ onChange,
+ language = 'json',
+ placeholder = '',
+ readOnly = false,
+ height = '300px',
+ className = '',
+ theme = 'light'
+}) => {
+ // Language extensions mapping
+ const getLanguageExtension = (lang) => {
+ switch (lang.toLowerCase()) {
+ case 'javascript':
+ case 'js':
+ return [javascript()];
+ case 'json':
+ return [json()];
+ case 'html':
+ return [html()];
+ case 'css':
+ return [css()];
+ default:
+ return [json()]; // Default to JSON
+ }
+ };
+
+ // Theme configuration
+ const getTheme = () => {
+ if (theme === 'dark') {
+ return oneDark;
+ }
+ return undefined; // Use default light theme
+ };
+
+ // Extensions
+ const extensions = [
+ ...getLanguageExtension(language),
+ EditorView.theme({
+ '&': {
+ fontSize: '14px',
+ },
+ '.cm-content': {
+ padding: '16px',
+ minHeight: height,
+ },
+ '.cm-focused': {
+ outline: 'none',
+ },
+ '.cm-editor': {
+ borderRadius: '8px',
+ },
+ '.cm-scroller': {
+ fontFamily: 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace',
+ },
+ }),
+ EditorView.lineWrapping,
+ ];
+
+ return (
+
+
+
+ );
+};
+
+export default CodeEditor;
diff --git a/src/components/Layout.js b/src/components/Layout.js
index 66e211ef..afd0dc58 100644
--- a/src/components/Layout.js
+++ b/src/components/Layout.js
@@ -1,8 +1,10 @@
import React, { useState, useEffect, useRef } from 'react';
-import { Link, useLocation } from 'react-router-dom';
-import { Home, Menu, X, ChevronDown, Terminal, Sparkles } from 'lucide-react';
-import ThemeToggle from './ThemeToggle';
+import { useLocation } from 'react-router-dom';
import ToolSidebar from './ToolSidebar';
+import NavigationConfirmModal from './NavigationConfirmModal';
+import useNavigationGuard from '../hooks/useNavigationGuard';
+import { Menu, X, ChevronDown, Terminal, Sparkles, Home } from 'lucide-react';
+import ThemeToggle from './ThemeToggle';
import SEOHead from './SEOHead';
import ConsentBanner from './ConsentBanner';
import { NON_TOOLS, TOOLS, SITE_CONFIG, getCategoryConfig } from '../config/tools';
@@ -10,6 +12,7 @@ import { useAnalytics } from '../hooks/useAnalytics';
const Layout = ({ children }) => {
const location = useLocation();
+ const { showModal, pendingNavigation, handleConfirm, handleCancel, hasUnsavedData, navigateWithGuard } = useNavigationGuard();
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
const dropdownRef = useRef(null);
@@ -43,6 +46,9 @@ const Layout = ({ children }) => {
// Check if we're on a tool page (not homepage)
const isToolPage = location.pathname !== '/';
+
+ // Check if we're on invoice preview page (no sidebar needed)
+ const isInvoicePreviewPage = location.pathname === '/invoice-preview';
return (
@@ -50,10 +56,10 @@ const Layout = ({ children }) => {
{/* Header */}
-
+
-
+