mirror of
https://github.com/PrimalHQ/primal-web-app.git
synced 2024-10-01 17:31:13 +00:00
Support Cashu Ecash
This commit is contained in:
parent
8f13127d6b
commit
c6807a9cd8
137
package-lock.json
generated
137
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.104.0",
|
"version": "0.104.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cashu/cashu-ts": "^0.9.0",
|
||||||
"@cookbook/solid-intl": "0.1.2",
|
"@cookbook/solid-intl": "0.1.2",
|
||||||
"@jukben/emoji-search": "3.0.0",
|
"@jukben/emoji-search": "3.0.0",
|
||||||
"@kobalte/core": "0.11.0",
|
"@kobalte/core": "0.11.0",
|
||||||
@ -491,6 +492,61 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@cashu/cashu-ts": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@cashu/cashu-ts/-/cashu-ts-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-DacSnpv3dJIGzo6A1nHIp2guFuDcmoPB5CX9m0SXA60bQxoIa4srHKLkjxUZ8GFCD9RaI+60UZ2+hZS635Ro2w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@gandlaf21/bolt11-decode": "^3.0.6",
|
||||||
|
"@noble/curves": "^1.0.0",
|
||||||
|
"@scure/bip32": "^1.3.2",
|
||||||
|
"@scure/bip39": "^1.2.1",
|
||||||
|
"buffer": "^6.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@cashu/cashu-ts/node_modules/@noble/curves": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/hashes": "1.4.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@cashu/cashu-ts/node_modules/@noble/hashes": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@cashu/cashu-ts/node_modules/@scure/base": {
|
||||||
|
"version": "1.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.6.tgz",
|
||||||
|
"integrity": "sha512-ok9AWwhcgYuGG3Zfhyqg+zwl+Wn5uE+dwC0NV/2qQkx4dABbb/bx96vWu8NSj+BNjjSjno+JRYRjle1jV08k3g==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@cashu/cashu-ts/node_modules/@scure/bip32": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@noble/curves": "~1.4.0",
|
||||||
|
"@noble/hashes": "~1.4.0",
|
||||||
|
"@scure/base": "~1.1.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@cookbook/solid-intl": {
|
"node_modules/@cookbook/solid-intl": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -635,6 +691,16 @@
|
|||||||
"tslib": "^2.4.0"
|
"tslib": "^2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@gandlaf21/bolt11-decode": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@gandlaf21/bolt11-decode/-/bolt11-decode-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-uorLVc3FAWO6USCaBHGixwUd7B86z4IVRa/TdLicsWi3Vm7QngYDIuUkOBemut0Qh/Qtsx47EjWMXS4WHel98A==",
|
||||||
|
"dependencies": {
|
||||||
|
"bech32": "^1.1.2",
|
||||||
|
"bn.js": "^4.11.8",
|
||||||
|
"buffer": "^6.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@internationalized/date": {
|
"node_modules/@internationalized/date": {
|
||||||
"version": "3.5.0",
|
"version": "3.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.5.0.tgz",
|
||||||
@ -1043,6 +1109,30 @@
|
|||||||
"@babel/core": "^7.0.0"
|
"@babel/core": "^7.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/base64-js": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"node_modules/bech32": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ=="
|
||||||
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -1050,6 +1140,11 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bn.js": {
|
||||||
|
"version": "4.12.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
|
||||||
|
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
|
||||||
|
},
|
||||||
"node_modules/braces": {
|
"node_modules/braces": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -1091,6 +1186,29 @@
|
|||||||
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer": {
|
||||||
|
"version": "6.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||||
|
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.1",
|
||||||
|
"ieee754": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001534",
|
"version": "1.0.30001534",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -1649,6 +1767,25 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/ieee754": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "patreon",
|
||||||
|
"url": "https://www.patreon.com/feross"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "consulting",
|
||||||
|
"url": "https://feross.org/support"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"vite-plugin-solid": "^2.5.0"
|
"vite-plugin-solid": "^2.5.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@cashu/cashu-ts": "0.9.0",
|
||||||
"@cookbook/solid-intl": "0.1.2",
|
"@cookbook/solid-intl": "0.1.2",
|
||||||
"@jukben/emoji-search": "3.0.0",
|
"@jukben/emoji-search": "3.0.0",
|
||||||
"@kobalte/core": "0.11.0",
|
"@kobalte/core": "0.11.0",
|
||||||
|
9
src/assets/icons/cashu.svg
Normal file
9
src/assets/icons/cashu.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 39 KiB |
335
src/components/Cashu/Cashu.module.scss
Normal file
335
src/components/Cashu/Cashu.module.scss
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
.cashu {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 158px;
|
||||||
|
background-color: var(--background-header-input);
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 12px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.paymentOverlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--background-site);
|
||||||
|
opacity: 0.6;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
color: var(--text-primary);
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerActions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
.qrIcon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: var(--text-secondary);
|
||||||
|
-webkit-mask: url(../../assets/icons/qr_code.svg) no-repeat 0px / 18px;
|
||||||
|
mask: url(../../assets/icons/qr_code.svg) no-repeat 0px / 18px;
|
||||||
|
}
|
||||||
|
.copyIcon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: var(--text-secondary);
|
||||||
|
-webkit-mask: url(../../assets/icons/copy_border.svg) no-repeat 0px / 18px;
|
||||||
|
mask: url(../../assets/icons/copy_border.svg) no-repeat 0px / 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.qrIcon, .copyIcon {
|
||||||
|
background-color: var(--text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyDone {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.checkIcon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: var(--success-bright);
|
||||||
|
-webkit-mask: url(../../assets/icons/check.svg) no-repeat 0px / 18px;
|
||||||
|
mask: url(../../assets/icons/check.svg) no-repeat 0px / 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: var(--text-primary);
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
color: var(--text-primary);
|
||||||
|
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
height: 36px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
.mint {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 0.15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payAction {
|
||||||
|
height: 36px;
|
||||||
|
min-width: 120px;
|
||||||
|
display: flex;
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spent {
|
||||||
|
height: 36px;
|
||||||
|
min-width: 120px;
|
||||||
|
margin-right: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.noBack {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cashuIcon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-image: url('../../assets/icons/cashu.svg');
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cashuAlter {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 158px;
|
||||||
|
background-color: none;
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 12px 0;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.paymentOverlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--background-site);
|
||||||
|
opacity: 0.6;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
height: 20px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerActions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
|
button {
|
||||||
|
.qrIcon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: white;
|
||||||
|
-webkit-mask: url(../../assets/icons/qr_code.svg) no-repeat 0px / 18px;
|
||||||
|
mask: url(../../assets/icons/qr_code.svg) no-repeat 0px / 18px;
|
||||||
|
}
|
||||||
|
.copyIcon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: white;
|
||||||
|
-webkit-mask: url(../../assets/icons/copy_border.svg) no-repeat 0px / 18px;
|
||||||
|
mask: url(../../assets/icons/copy_border.svg) no-repeat 0px / 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.qrIcon, .copyIcon {
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyDone {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.checkIcon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px;
|
||||||
|
background-color: var(--success-bright);
|
||||||
|
-webkit-mask: url(../../assets/icons/check.svg) no-repeat 0px / 18px;
|
||||||
|
mask: url(../../assets/icons/check.svg) no-repeat 0px / 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
height: 36px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
.expiryDate {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 0.15px;
|
||||||
|
}
|
||||||
|
.expiredDate {
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 0.15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payAction {
|
||||||
|
height: 36px;
|
||||||
|
min-width: 120px;
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--accent);
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.noBack {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cashuIcon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-image: url('../../assets/icons/cashu.svg');
|
||||||
|
background-size: contain;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
}
|
185
src/components/Cashu/Cashu.tsx
Normal file
185
src/components/Cashu/Cashu.tsx
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import { Component, createEffect, createSignal, Match, onMount, Show, Switch } from 'solid-js';
|
||||||
|
import { hookForDev } from '../../lib/devTools';
|
||||||
|
|
||||||
|
import styles from './Cashu.module.scss';
|
||||||
|
import { createStore } from 'solid-js/store';
|
||||||
|
import ButtonPrimary from '../Buttons/ButtonPrimary';
|
||||||
|
import ButtonGhost from '../Buttons/ButtonGhost';
|
||||||
|
import { useAppContext } from '../../contexts/AppContext';
|
||||||
|
import Loader from '../Loader/Loader';
|
||||||
|
import { logError } from '../../lib/logger';
|
||||||
|
import { useIntl } from '@cookbook/solid-intl';
|
||||||
|
import { cashuInvoice } from '../../translations';
|
||||||
|
import { getDecodedToken, Token, TokenEntry } from "@cashu/cashu-ts";
|
||||||
|
import { useAccountContext } from '../../contexts/AccountContext';
|
||||||
|
|
||||||
|
|
||||||
|
const Cashu: Component< { id?: string, token: string, alternative?: boolean, noBack?: boolean } > = (props) => {
|
||||||
|
|
||||||
|
const account = useAccountContext();
|
||||||
|
const app = useAppContext();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [invoice, setInvoice] = createStore<Token>({ token: [] });
|
||||||
|
const [cashuSpendable, setCashuSpendable] = createSignal<boolean>(false);
|
||||||
|
|
||||||
|
const [invoiceCopied, setInvoiceCopied] = createSignal(false);
|
||||||
|
|
||||||
|
const [paymentInProgress, setPaymentInProgress] = createSignal(false);
|
||||||
|
|
||||||
|
const checkMints = async (entries: TokenEntry[]) => {
|
||||||
|
let statuses: boolean[] = [];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
const mint = app?.actions.getCashuMint(entry.mint);
|
||||||
|
if (!mint) continue;
|
||||||
|
|
||||||
|
const spent = await mint.check({ proofs: entry.proofs.map((p) => ({ secret: p.secret })) });
|
||||||
|
|
||||||
|
const data = spent.spendable.map(s => s);
|
||||||
|
|
||||||
|
statuses = [ ...statuses, ...data];
|
||||||
|
}
|
||||||
|
|
||||||
|
setCashuSpendable(() => !statuses.includes(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (invoice.token.length === 0) return;
|
||||||
|
|
||||||
|
checkMints(invoice.token);
|
||||||
|
});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
try {
|
||||||
|
const dec: Token = getDecodedToken(props.token);
|
||||||
|
setInvoice(() => ({ ...dec }));
|
||||||
|
} catch (e) {
|
||||||
|
logError('Failed to decode cashu token: ', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (invoiceCopied()) {
|
||||||
|
setTimeout(() => {
|
||||||
|
setInvoiceCopied(() => false);
|
||||||
|
}, 1_000);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const amount = () =>
|
||||||
|
`${invoice.token[0]?.proofs.reduce((acc, v) => acc + v.amount, 0) || 0} sats`;
|
||||||
|
|
||||||
|
const description = () => invoice.memo || '';
|
||||||
|
|
||||||
|
const confirmPayment = () => app?.actions.openConfirmModal({
|
||||||
|
title: intl.formatMessage(cashuInvoice.confirm.title),
|
||||||
|
description: intl.formatMessage(cashuInvoice.confirm.description, { amount: amount() }),
|
||||||
|
confirmLabel: intl.formatMessage(cashuInvoice.confirm.confirmLabel),
|
||||||
|
abortLabel: intl.formatMessage(cashuInvoice.confirm.abortLabel),
|
||||||
|
onAbort: app.actions.closeConfirmModal,
|
||||||
|
onConfirm: () => {
|
||||||
|
app.actions.closeConfirmModal();
|
||||||
|
redeemCashu();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const redeemCashu = () => {
|
||||||
|
const lnurl = account?.activeUser?.lud16 ?? '';
|
||||||
|
const url = `https://redeem.cashu.me?token=${encodeURIComponent(props.token)}&lightning=${encodeURIComponent(
|
||||||
|
lnurl,
|
||||||
|
)}&autopay=yes`;
|
||||||
|
|
||||||
|
console.log('Redeem: ', url);
|
||||||
|
|
||||||
|
window.open(url, 'blank_');
|
||||||
|
};
|
||||||
|
|
||||||
|
const klass = () => {
|
||||||
|
let k = props.alternative ? styles.cashuAlter : styles.cashu;
|
||||||
|
if (props.noBack) {
|
||||||
|
k += ` ${styles.noBack}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return k;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id={props.id} class={klass()}>
|
||||||
|
<Show when={paymentInProgress()}>
|
||||||
|
<div class={styles.paymentOverlay}>
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<div class={styles.header}>
|
||||||
|
<div class={styles.title}>
|
||||||
|
<div class={styles.cashuIcon}></div>
|
||||||
|
<div>{intl.formatMessage(cashuInvoice.title)}</div>
|
||||||
|
</div>
|
||||||
|
<div class={styles.headerActions}>
|
||||||
|
<Show when={cashuSpendable()}>
|
||||||
|
<ButtonGhost
|
||||||
|
onClick={(e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
app?.actions.openCashuModal(props.token, () => {
|
||||||
|
app.actions.closeCashuModal();
|
||||||
|
confirmPayment();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
shrink={true}
|
||||||
|
>
|
||||||
|
<div class={styles.qrIcon}></div>
|
||||||
|
</ButtonGhost>
|
||||||
|
</Show>
|
||||||
|
|
||||||
|
<Show
|
||||||
|
when={!invoiceCopied()}
|
||||||
|
fallback={<div class={styles.copyDone}><div class={styles.checkIcon}></div></div>}
|
||||||
|
>
|
||||||
|
<ButtonGhost
|
||||||
|
onClick={(e: MouseEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
navigator.clipboard.writeText(props.token);
|
||||||
|
setInvoiceCopied(() => true);
|
||||||
|
}}
|
||||||
|
shrink={true}
|
||||||
|
>
|
||||||
|
<div class={styles.copyIcon}></div>
|
||||||
|
</ButtonGhost>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class={styles.body}>
|
||||||
|
<div class={styles.description}>{description()}</div>
|
||||||
|
<div class={styles.amount}>{amount()}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={styles.footer}>
|
||||||
|
<div class={styles.mint}>
|
||||||
|
<Show when={invoice.token[0]}>
|
||||||
|
{intl.formatMessage(cashuInvoice.mint, { url: new URL(invoice.token[0]?.mint).hostname })}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<Show
|
||||||
|
when={cashuSpendable()}
|
||||||
|
fallback={(
|
||||||
|
<div class={styles.spent}>
|
||||||
|
{intl.formatMessage(cashuInvoice.spent)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div class={styles.payAction}>
|
||||||
|
<ButtonPrimary onClick={(e: MouseEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
confirmPayment();
|
||||||
|
}}>
|
||||||
|
{intl.formatMessage(cashuInvoice.redeem)}
|
||||||
|
</ButtonPrimary>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hookForDev(Cashu);
|
120
src/components/CashuQrCodeModal/CashuQrCodeModal.module.scss
Normal file
120
src/components/CashuQrCodeModal/CashuQrCodeModal.module.scss
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
.CashuQrCodeModal {
|
||||||
|
position: fixed;
|
||||||
|
min-width: 472px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
background-color: var(--background-input);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 20px 24px 28px 24px;
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
border: none;
|
||||||
|
outline: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0px 0px;
|
||||||
|
background-color: var(--text-secondary);
|
||||||
|
-webkit-mask: url(../../assets/icons/close.svg) no-repeat center;
|
||||||
|
mask: url(../../assets/icons/close.svg) no-repeat center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--text-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.description {
|
||||||
|
color: var(--text-primary);
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount {
|
||||||
|
color: var(--text-primary);
|
||||||
|
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.separator {
|
||||||
|
width: 100%;
|
||||||
|
height: 1px;
|
||||||
|
border: 1px solid var(--subtile-devider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
height: 36px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-top: 20px;
|
||||||
|
|
||||||
|
.mint {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 18px;
|
||||||
|
letter-spacing: 0.15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payAction {
|
||||||
|
height: 36px;
|
||||||
|
min-width: 120px;
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.zapIcon {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 9px;
|
||||||
|
background: var(--sidebar-section-icon-gradient);
|
||||||
|
-webkit-mask: url(../../assets/icons/explore/zaps_hollow.svg) no-repeat 2px 0 / 19px 22px;
|
||||||
|
mask: url(../../assets/icons/explore/zaps_hollow.svg) no-repeat 2px 0 / 19px 22px;
|
||||||
|
}
|
83
src/components/CashuQrCodeModal/CashuQrCodeModal.tsx
Normal file
83
src/components/CashuQrCodeModal/CashuQrCodeModal.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { useIntl } from '@cookbook/solid-intl';
|
||||||
|
// @ts-ignore
|
||||||
|
import { decode } from 'light-bolt11-decoder';
|
||||||
|
import { Component, createEffect, Show } from 'solid-js';
|
||||||
|
import { createStore, reconcile } from 'solid-js/store';
|
||||||
|
import { emptyInvoice } from '../../constants';
|
||||||
|
import { date, dateFuture } from '../../lib/dates';
|
||||||
|
import { hookForDev } from '../../lib/devTools';
|
||||||
|
import { humanizeNumber } from '../../lib/stats';
|
||||||
|
import { cashuInvoice } from '../../translations';
|
||||||
|
import { LnbcInvoice } from '../../types/primal';
|
||||||
|
import ButtonPrimary from '../Buttons/ButtonPrimary';
|
||||||
|
import Modal from '../Modal/Modal';
|
||||||
|
import QrCode from '../QrCode/QrCode';
|
||||||
|
import { getDecodedToken, Token } from "@cashu/cashu-ts";
|
||||||
|
|
||||||
|
import styles from './CashuQrCodeModal.module.scss';
|
||||||
|
|
||||||
|
const CashuQrCodeModal: Component<{
|
||||||
|
id?: string,
|
||||||
|
open?: boolean,
|
||||||
|
cashu: string | undefined,
|
||||||
|
onPay?: () => void,
|
||||||
|
onClose?: () => void,
|
||||||
|
}> = (props) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const [invoice, setInvoice] = createStore<Token>({ token: []});
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
if (props.cashu) {
|
||||||
|
const dec: Token = getDecodedToken(props.cashu);
|
||||||
|
setInvoice(reconcile(dec));
|
||||||
|
} else {
|
||||||
|
setInvoice(reconcile({ token: [] }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const amount = () =>
|
||||||
|
`${invoice.token[0]?.proofs.reduce((acc, v) => acc + v.amount, 0) || 0} sats`;
|
||||||
|
|
||||||
|
const description = () => invoice.memo || '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={props.open} onClose={props.onClose}>
|
||||||
|
<div id={props.id} class={styles.CashuQrCodeModal}>
|
||||||
|
<div class={styles.header}>
|
||||||
|
<div class={styles.title}>
|
||||||
|
{intl.formatMessage(cashuInvoice.title)}
|
||||||
|
</div>
|
||||||
|
<button class={styles.close} onClick={props.onClose}>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={styles.body}>
|
||||||
|
<div class={styles.qrCode}>
|
||||||
|
<QrCode data={props.cashu || ''} type="lightning"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={styles.description}>{description()}</div>
|
||||||
|
<div class={styles.amount}>{amount()}</div>
|
||||||
|
|
||||||
|
<div class={styles.separator}></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class={styles.footer}>
|
||||||
|
<div class={styles.mint}>
|
||||||
|
<Show when={invoice.token[0]}>
|
||||||
|
{intl.formatMessage(cashuInvoice.mint, { url: new URL(invoice.token[0]?.mint).hostname })}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
<div class={styles.payAction}>
|
||||||
|
<ButtonPrimary onClick={props.onPay}>
|
||||||
|
{intl.formatMessage(cashuInvoice.redeem)}
|
||||||
|
</ButtonPrimary>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default hookForDev(CashuQrCodeModal);
|
@ -20,6 +20,7 @@ import CustomZap from '../CustomZap/CustomZap';
|
|||||||
import NoteContextMenu from '../Note/NoteContextMenu';
|
import NoteContextMenu from '../Note/NoteContextMenu';
|
||||||
import LnQrCodeModal from '../LnQrCodeModal/LnQrCodeModal';
|
import LnQrCodeModal from '../LnQrCodeModal/LnQrCodeModal';
|
||||||
import ConfirmModal from '../ConfirmModal/ConfirmModal';
|
import ConfirmModal from '../ConfirmModal/ConfirmModal';
|
||||||
|
import CashuQrCodeModal from '../CashuQrCodeModal/CashuQrCodeModal';
|
||||||
|
|
||||||
export const [isHome, setIsHome] = createSignal(false);
|
export const [isHome, setIsHome] = createSignal(false);
|
||||||
|
|
||||||
@ -174,6 +175,13 @@ const Layout: Component = () => {
|
|||||||
onClose={app?.lnbc?.onCancel}
|
onClose={app?.lnbc?.onCancel}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<CashuQrCodeModal
|
||||||
|
open={app?.showCashuInvoiceModal}
|
||||||
|
cashu={app?.cashu?.invoice || ''}
|
||||||
|
onPay={app?.cashu?.onPay}
|
||||||
|
onClose={app?.cashu?.onCancel}
|
||||||
|
/>
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
open={app?.showConfirmModal}
|
open={app?.showConfirmModal}
|
||||||
title={app?.confirmInfo?.title}
|
title={app?.confirmInfo?.title}
|
||||||
|
@ -8,6 +8,8 @@ import {
|
|||||||
useContext
|
useContext
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import { PrimalNote, PrimalUser, ZapOption } from "../types/primal";
|
import { PrimalNote, PrimalUser, ZapOption } from "../types/primal";
|
||||||
|
import { CashuMint } from "@cashu/cashu-ts";
|
||||||
|
|
||||||
|
|
||||||
export type ReactionStats = {
|
export type ReactionStats = {
|
||||||
likes: number,
|
likes: number,
|
||||||
@ -41,7 +43,7 @@ export type ConfirmInfo = {
|
|||||||
onAbort?: () => void,
|
onAbort?: () => void,
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LnbcInfo = {
|
export type InvoiceInfo = {
|
||||||
invoice: string,
|
invoice: string,
|
||||||
onPay?: () => void,
|
onPay?: () => void,
|
||||||
onCancel?: () => void,
|
onCancel?: () => void,
|
||||||
@ -57,9 +59,12 @@ export type AppContextStore = {
|
|||||||
showNoteContextMenu: boolean,
|
showNoteContextMenu: boolean,
|
||||||
noteContextMenuInfo: NoteContextMenuInfo | undefined,
|
noteContextMenuInfo: NoteContextMenuInfo | undefined,
|
||||||
showLnInvoiceModal: boolean,
|
showLnInvoiceModal: boolean,
|
||||||
lnbc: LnbcInfo | undefined,
|
lnbc: InvoiceInfo | undefined,
|
||||||
|
showCashuInvoiceModal: boolean,
|
||||||
|
cashu: InvoiceInfo | undefined,
|
||||||
showConfirmModal: boolean,
|
showConfirmModal: boolean,
|
||||||
confirmInfo: ConfirmInfo | undefined,
|
confirmInfo: ConfirmInfo | undefined,
|
||||||
|
cashuMints: Map<string, CashuMint>,
|
||||||
actions: {
|
actions: {
|
||||||
openReactionModal: (noteId: string, stats: ReactionStats) => void,
|
openReactionModal: (noteId: string, stats: ReactionStats) => void,
|
||||||
closeReactionModal: () => void,
|
closeReactionModal: () => void,
|
||||||
@ -69,8 +74,11 @@ export type AppContextStore = {
|
|||||||
closeContextMenu: () => void,
|
closeContextMenu: () => void,
|
||||||
openLnbcModal: (lnbc: string, onPay: () => void) => void,
|
openLnbcModal: (lnbc: string, onPay: () => void) => void,
|
||||||
closeLnbcModal: () => void,
|
closeLnbcModal: () => void,
|
||||||
|
openCashuModal: (cashu: string, onPay: () => void) => void,
|
||||||
|
closeCashuModal: () => void,
|
||||||
openConfirmModal: (confirmInfo: ConfirmInfo) => void,
|
openConfirmModal: (confirmInfo: ConfirmInfo) => void,
|
||||||
closeConfirmModal: () => void,
|
closeConfirmModal: () => void,
|
||||||
|
getCashuMint: (url: string) => CashuMint | undefined,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,8 +98,11 @@ const initialData: Omit<AppContextStore, 'actions'> = {
|
|||||||
noteContextMenuInfo: undefined,
|
noteContextMenuInfo: undefined,
|
||||||
showLnInvoiceModal: false,
|
showLnInvoiceModal: false,
|
||||||
lnbc: undefined,
|
lnbc: undefined,
|
||||||
|
showCashuInvoiceModal: false,
|
||||||
|
cashu: undefined,
|
||||||
showConfirmModal: false,
|
showConfirmModal: false,
|
||||||
confirmInfo: undefined,
|
confirmInfo: undefined,
|
||||||
|
cashuMints: new Map(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AppContext = createContext<AppContextStore>();
|
export const AppContext = createContext<AppContextStore>();
|
||||||
@ -165,6 +176,21 @@ export const AppProvider = (props: { children: JSXElement }) => {
|
|||||||
updateStore('lnbc', () => undefined);
|
updateStore('lnbc', () => undefined);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const openCashuModal = (cashu: string, onPay: () => void) => {
|
||||||
|
updateStore('showCashuInvoiceModal', () => true);
|
||||||
|
updateStore('cashu', () => ({
|
||||||
|
invoice: cashu,
|
||||||
|
onPay,
|
||||||
|
onCancel: () => updateStore('showCashuInvoiceModal', () => false),
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeCashuModal = () => {
|
||||||
|
updateStore('showCashuInvoiceModal', () => false);
|
||||||
|
updateStore('cashu', () => undefined);
|
||||||
|
};
|
||||||
|
|
||||||
const openConfirmModal = (confirmInfo: ConfirmInfo) => {
|
const openConfirmModal = (confirmInfo: ConfirmInfo) => {
|
||||||
updateStore('showConfirmModal', () => true);
|
updateStore('showConfirmModal', () => true);
|
||||||
updateStore('confirmInfo', () => ({...confirmInfo }));
|
updateStore('confirmInfo', () => ({...confirmInfo }));
|
||||||
@ -179,6 +205,15 @@ export const AppProvider = (props: { children: JSXElement }) => {
|
|||||||
updateStore('showNoteContextMenu', () => false);
|
updateStore('showNoteContextMenu', () => false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCashuMint = (url: string) => {
|
||||||
|
const formatted = new URL(url).toString();
|
||||||
|
if (!store.cashuMints.has(formatted)) {
|
||||||
|
const mint = new CashuMint(formatted);
|
||||||
|
store.cashuMints.set(formatted, mint);
|
||||||
|
}
|
||||||
|
return store.cashuMints.get(formatted);
|
||||||
|
};
|
||||||
|
|
||||||
// EFFECTS --------------------------------------
|
// EFFECTS --------------------------------------
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@ -227,6 +262,9 @@ export const AppProvider = (props: { children: JSXElement }) => {
|
|||||||
closeLnbcModal,
|
closeLnbcModal,
|
||||||
openConfirmModal,
|
openConfirmModal,
|
||||||
closeConfirmModal,
|
closeConfirmModal,
|
||||||
|
openCashuModal,
|
||||||
|
closeCashuModal,
|
||||||
|
getCashuMint,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -36,6 +36,7 @@ import PageCaption from '../components/PageCaption/PageCaption';
|
|||||||
import { useMediaContext } from '../contexts/MediaContext';
|
import { useMediaContext } from '../contexts/MediaContext';
|
||||||
import PageTitle from '../components/PageTitle/PageTitle';
|
import PageTitle from '../components/PageTitle/PageTitle';
|
||||||
import Lnbc from '../components/Lnbc/Lnbc';
|
import Lnbc from '../components/Lnbc/Lnbc';
|
||||||
|
import Cashu from '../components/Cashu/Cashu';
|
||||||
|
|
||||||
type AutoSizedTextArea = HTMLTextAreaElement & { _baseScrollHeight: number };
|
type AutoSizedTextArea = HTMLTextAreaElement & { _baseScrollHeight: number };
|
||||||
|
|
||||||
@ -840,6 +841,13 @@ const Messages: Component = () => {
|
|||||||
return test
|
return test
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const msgHasCashu = (msg: DirectMessage) => {
|
||||||
|
const r =/(\s+|\r\n|\r|\n|^)cashuA[a-zA-Z0-9]+/;
|
||||||
|
const test = r.test(msg.content);
|
||||||
|
|
||||||
|
return test
|
||||||
|
};
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (account?.hasPublicKey()) {
|
if (account?.hasPublicKey()) {
|
||||||
profile?.actions.setProfileKey(account.publicKey)
|
profile?.actions.setProfileKey(account.publicKey)
|
||||||
@ -894,7 +902,7 @@ const Messages: Component = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderMessage = (msg: DirectMessage, thread: DirectMessageThread) => {
|
const renderMessage = (msg: DirectMessage, thread: DirectMessageThread) => {
|
||||||
if (!msgHasInvoice(msg)) {
|
if (!msgHasInvoice(msg) && !msgHasCashu(msg)) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
class={styles.message}
|
class={styles.message}
|
||||||
@ -913,7 +921,7 @@ const Messages: Component = () => {
|
|||||||
|
|
||||||
let sectionIndex = 0;
|
let sectionIndex = 0;
|
||||||
tokens.forEach((t) => {
|
tokens.forEach((t) => {
|
||||||
if (t.startsWith('lnbc')) {
|
if (t.startsWith('lnbc') || t.startsWith('cashuA')) {
|
||||||
if (sections[sectionIndex]) sectionIndex++;
|
if (sections[sectionIndex]) sectionIndex++;
|
||||||
|
|
||||||
sections[sectionIndex] = t;
|
sections[sectionIndex] = t;
|
||||||
@ -956,6 +964,15 @@ const Messages: Component = () => {
|
|||||||
<Lnbc lnbc={section} noBack={true} alternative={!isSelectedSender(thread.author)} />
|
<Lnbc lnbc={section} noBack={true} alternative={!isSelectedSender(thread.author)} />
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
|
<Match when={section.startsWith('cashuA')}>
|
||||||
|
<div
|
||||||
|
class={styles.messageLn}
|
||||||
|
data-event-id={msg.id}
|
||||||
|
title={date(msg.created_at || 0).date.toLocaleString()}
|
||||||
|
>
|
||||||
|
<Cashu token={section} noBack={true} alternative={!isSelectedSender(thread.author)} />
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
</Switch>
|
</Switch>
|
||||||
)}
|
)}
|
||||||
</For>
|
</For>
|
||||||
|
@ -2199,3 +2199,54 @@ export const lnInvoice = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cashuInvoice = {
|
||||||
|
redeem: {
|
||||||
|
id: 'cashuInvoice.redeem',
|
||||||
|
defaultMessage: 'Reedem',
|
||||||
|
description: 'Reedem ecash action',
|
||||||
|
},
|
||||||
|
pending: {
|
||||||
|
id: 'cashuInvoice.pending',
|
||||||
|
defaultMessage: 'Pending',
|
||||||
|
description: 'Pending ecash',
|
||||||
|
},
|
||||||
|
spent: {
|
||||||
|
id: 'cashuInvoice.spent',
|
||||||
|
defaultMessage: 'Spent',
|
||||||
|
description: 'Spent ecash',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
id: 'cashuInvoice.title',
|
||||||
|
defaultMessage: 'Cashu Ecash',
|
||||||
|
description: 'Cashu Ecash title',
|
||||||
|
},
|
||||||
|
mint: {
|
||||||
|
id: 'cashuInvoice.mint',
|
||||||
|
defaultMessage: 'Mint: {url}',
|
||||||
|
description: 'Mint url',
|
||||||
|
},
|
||||||
|
confirm: {
|
||||||
|
title: {
|
||||||
|
id: 'cashuInvoice.confirm.title',
|
||||||
|
defaultMessage: 'Are you sure?',
|
||||||
|
description: 'Cashu invoice pay confirmation',
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
id: 'cashuInvoice.confirm.description',
|
||||||
|
defaultMessage: 'Redeem {amount}',
|
||||||
|
description: 'Cashu Invoice confirm description',
|
||||||
|
},
|
||||||
|
confirmLabel: {
|
||||||
|
id: 'cashuInvoice.confirm.confirmLabel',
|
||||||
|
defaultMessage: 'Yes, redeem',
|
||||||
|
description: 'Cashu Invoice confirm button label',
|
||||||
|
},
|
||||||
|
abortLabel: {
|
||||||
|
id: 'cashuInvoice.confirm.abortLabel',
|
||||||
|
defaultMessage: 'Cancel',
|
||||||
|
description: 'Cashu Invoice confirm button label',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user