- The WebLN payment failed. You can still scan the QR code with a
- Lightning wallet.
-
+ The WebLN payment failed. You can still scan the QR code with a
+ Lightning wallet.
+
{{/if}}
{{! Step: Success }}
{{#if (eq this.step "success")}}
-
-
⚡
+
+
⚡
Zap Sent!
You zapped
@@ -520,27 +490,25 @@ export default class ZapPhotoModal extends Component {
{{! Step: Error }}
{{#if (eq this.step "error")}}
-
-
{{this.error}}
-
-
-
-
+
{{this.error}}
+
+
+
{{/if}}
-
+
}
diff --git a/app/styles/app.css b/app/styles/app.css
index 5a51211..6fe69d5 100644
--- a/app/styles/app.css
+++ b/app/styles/app.css
@@ -2427,259 +2427,113 @@ button.create-place {
border-radius: 10px;
}
-/* Zap Photo Modal */
-.zap-photo-modal-overlay {
- position: fixed;
- inset: 0;
- background: rgb(0 0 0 / 70%);
- z-index: 10000;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 1rem;
+/* Generic modal heading reset (shared by all modals) */
+.modal-content h2,
+.modal-content h3 {
+ margin-top: 0;
}
+/* Zap Photo Modal — scoped, nested styles */
.zap-photo-modal {
- background: white;
- border-radius: 12px;
- box-shadow: 0 8px 32px rgb(0 0 0 / 30%);
- width: 100%;
- max-width: 420px;
- max-height: 90vh;
- overflow-y: auto;
- display: flex;
- flex-direction: column;
-}
-
-.zap-photo-modal-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 1rem 1.25rem;
- border-bottom: 1px solid var(--divider-color);
-}
-
-.zap-photo-modal-header h3 {
- margin: 0;
-}
-
-.zap-modal-close {
- background: none;
- border: none;
- padding: 0.25rem;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #898989;
-}
-
-.zap-modal-close:hover {
- color: var(--body-text-color);
-}
-
-.zap-modal-status {
- padding: 0.75rem 1.25rem;
- border-bottom: 1px solid var(--divider-color);
- min-height: 2.5rem;
- display: flex;
- flex-direction: column;
- gap: 0.4rem;
-}
-
-.zap-modal-loading {
- font-size: 0.85rem;
- color: #898989;
-}
-
-.zap-modal-address {
- display: flex;
- align-items: center;
- gap: 0.5rem;
-}
-
-.zap-modal-badge {
- background: #f7b500;
- color: white;
- font-size: 0.7rem;
- font-weight: 600;
- padding: 2px 8px;
- border-radius: 10px;
- text-transform: uppercase;
- letter-spacing: 0.5px;
-}
-
-.zap-modal-address-text {
- font-family: monospace;
- font-size: 0.8rem;
- color: #555;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.zap-modal-error-msg {
- color: var(--danger-color);
- font-size: 0.85rem;
- padding: 0.5rem 0.75rem;
- background: rgb(234 67 53 / 8%);
- border-radius: 4px;
-}
-
-.zap-modal-body {
- padding: 1.25rem;
- display: flex;
- flex-direction: column;
- gap: 1rem;
-}
-
-.zap-modal-center {
- align-items: center;
text-align: center;
-}
-.zap-amount-display {
- display: flex;
- align-items: baseline;
- justify-content: center;
- gap: 0.4rem;
-}
+ & h2 {
+ margin-bottom: 1rem;
+ }
-.zap-amount-value {
- font-size: 2rem;
- font-weight: 700;
- color: #f7b500;
-}
+ & .amount-display {
+ display: flex;
+ align-items: baseline;
+ justify-content: center;
+ gap: 0.4rem;
+ }
-.zap-amount-unit {
- font-size: 0.9rem;
- color: #898989;
-}
+ & .amount-value {
+ font-size: 2rem;
+ font-weight: 700;
+ color: var(--default-list-color);
+ }
-.zap-slider {
- width: 100%;
- accent-color: #f7b500;
- cursor: pointer;
-}
+ & .amount-unit {
+ font-size: 0.9rem;
+ color: var(--body-text-color);
+ opacity: 0.7;
+ }
-.zap-slider-ticks {
- display: flex;
- justify-content: space-between;
- font-size: 0.7rem;
- color: #aaa;
- padding: 0 0.25rem;
-}
+ & .zap-slider {
+ width: 100%;
+ accent-color: var(--default-list-color);
+ cursor: pointer;
+ }
-.zap-min-max {
- font-size: 0.75rem;
- color: #898989;
- text-align: center;
-}
+ & .slider-ticks {
+ display: flex;
+ justify-content: space-between;
+ font-size: 0.7rem;
+ color: var(--body-text-color);
+ opacity: 0.5;
+ padding: 0 0.25rem;
+ }
-.zap-message-field {
- display: flex;
- flex-direction: column;
- gap: 0.3rem;
-}
+ & .min-max {
+ font-size: 0.75rem;
+ color: var(--body-text-color);
+ opacity: 0.7;
+ }
-.zap-message-field label {
- font-size: 0.8rem;
- color: #898989;
-}
+ & .lightning-address {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ }
-.zap-message-field textarea {
- width: 100%;
- border: 1px solid #ddd;
- border-radius: 4px;
- padding: 0.5rem;
- font-family: inherit;
- font-size: 0.9rem;
- resize: vertical;
-}
+ & .lightning-badge {
+ background: var(--default-list-color);
+ color: white;
+ font-size: 0.7rem;
+ font-weight: 600;
+ padding: 2px 8px;
+ border-radius: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ }
-.zap-message-field textarea:focus {
- outline: none;
- border-color: var(--link-color);
-}
+ & .lightning-address-text {
+ font-family: monospace;
+ font-size: 0.8rem;
+ color: var(--body-text-color);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
-.zap-modal-actions {
- display: flex;
- gap: 0.5rem;
- justify-content: flex-end;
-}
+ & .centered {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 1rem;
+ }
-.zap-modal-hint {
- font-size: 0.8rem;
- color: #898989;
- text-align: center;
-}
+ & .success {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 0.5rem;
+ }
-.zap-spinner {
- width: 32px;
- height: 32px;
- border: 3px solid #eee;
- border-top-color: #f7b500;
- border-radius: 50%;
- animation: zap-spin 0.8s linear infinite;
-}
+ & .success-icon {
+ font-size: 3rem;
+ }
-@keyframes zap-spin {
- to {
- transform: rotate(360deg);
+ & .success h4 {
+ margin: 0;
+ font-size: 1.2rem;
+ }
+
+ & .success p {
+ margin: 0;
+ font-size: 0.9rem;
+ color: var(--body-text-color);
}
}
-
-.zap-pay-prompt {
- text-align: center;
- font-size: 0.9rem;
-}
-
-.zap-qr-container {
- display: flex;
- justify-content: center;
-}
-
-.zap-qr-container canvas {
- border-radius: 8px;
- background: white;
- padding: 8px;
-}
-
-.zap-qr-container a {
- display: block;
-}
-
-.zap-webln-btn {
- width: 100%;
-}
-
-.zap-open-wallet-link {
- width: 100%;
- text-align: center;
- text-decoration: none;
-}
-
-.zap-invoice-details {
- display: flex;
- justify-content: center;
- font-size: 0.8rem;
-}
-
-.zap-success {
- gap: 0.5rem;
-}
-
-.zap-success-icon {
- font-size: 3rem;
-}
-
-.zap-success h4 {
- margin: 0;
- font-size: 1.2rem;
-}
-
-.zap-success p {
- margin: 0;
- font-size: 0.9rem;
- color: #555;
-}
diff --git a/tests/integration/components/zap-photo-modal-test.gjs b/tests/integration/components/zap-photo-modal-test.gjs
index 4c58aae..f08a13e 100644
--- a/tests/integration/components/zap-photo-modal-test.gjs
+++ b/tests/integration/components/zap-photo-modal-test.gjs
@@ -42,11 +42,11 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
assert.dom('.zap-slider').exists('slider is rendered');
- assert.dom('.zap-amount-value').exists('amount is displayed');
+ assert.dom('.zap-amount').exists('amount is displayed');
assert
- .dom('.zap-modal-address-text')
+ .dom('.lightning-address-text')
.hasText('user@example.com', 'lightning address is shown');
- assert.dom('.zap-modal-badge').hasText('Lightning', 'badge is shown');
+ assert.dom('.lightning-badge').hasText('Lightning', 'badge is shown');
});
test('it shows error when user has no lightning address', async function (assert) {
@@ -60,10 +60,10 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
assert
- .dom('.zap-modal-error-msg')
+ .dom('.alert-error')
.exists('error message is shown when no lightning address');
assert
- .dom('.zap-modal-actions .btn-primary')
+ .dom('.edit-actions .btn-primary')
.isDisabled('zap button is disabled when no address');
});
@@ -80,11 +80,9 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
+ assert.dom('.alert-error').exists('error shown when allowsNostr is false');
assert
- .dom('.zap-modal-error-msg')
- .exists('error shown when allowsNostr is false');
- assert
- .dom('.zap-modal-actions .btn-primary')
+ .dom('.edit-actions .btn-primary')
.isDisabled('zap button is disabled');
});
@@ -103,10 +101,10 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
assert
- .dom('.zap-modal-actions .btn-primary')
+ .dom('.edit-actions .btn-primary')
.isNotDisabled('zap button is enabled when endpoint is valid');
- await click('.zap-modal-actions .btn-primary');
+ await click('.edit-actions .btn-primary');
assert.ok(zapSpy.calledOnce, 'nostrZap.zap was called');
assert.deepEqual(
@@ -120,9 +118,9 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
'zap amount is 1000 sats in millisats'
);
- await waitFor('.zap-qr-container');
+ await waitFor('.qr-code-container');
- assert.dom('.zap-qr-container').exists('QR code is shown');
+ assert.dom('.qr-code-container').exists('QR code is shown');
assert.dom('.zap-pay-prompt').exists('payment prompt is shown');
assert.notOk(closed, 'modal was not closed by clicking the Zap button');
});
@@ -137,9 +135,9 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
- await fillIn('.zap-message-field textarea', 'Great photo!');
+ await fillIn('.form-group textarea', 'Great photo!');
- await click('.zap-modal-actions .btn-primary');
+ await click('.edit-actions .btn-primary');
assert.ok(zapSpy.calledOnce, 'nostrZap.zap was called');
assert.strictEqual(
@@ -159,10 +157,10 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
- await click('.zap-modal-actions .btn-primary');
- await waitFor('.zap-webln-btn');
+ await click('.edit-actions .btn-primary');
+ await waitFor('.webln-pay-btn');
- assert.dom('.zap-webln-btn').exists('WebLN button is shown when available');
+ assert.dom('.webln-pay-btn').exists('WebLN button is shown when available');
});
test('it shows success after WebLN payment', async function (assert) {
@@ -175,9 +173,9 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
- await click('.zap-modal-actions .btn-primary');
- await waitFor('.zap-webln-btn');
- await click('.zap-webln-btn');
+ await click('.edit-actions .btn-primary');
+ await waitFor('.webln-pay-btn');
+ await click('.webln-pay-btn');
await waitFor('.zap-success');
@@ -193,10 +191,10 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
- await click('.zap-modal-actions .btn-primary');
- await waitFor('.zap-qr-container');
+ await click('.edit-actions .btn-primary');
+ await waitFor('.qr-code-container');
- assert.dom('.zap-qr-container').exists('QR code is shown');
+ assert.dom('.qr-code-container').exists('QR code is shown');
assert.ok(
this.nostrZap._receiptCallback,
@@ -227,7 +225,7 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
- await click('.zap-modal-close');
+ await click('.close-modal-btn');
assert.ok(closed, 'onClose was called');
});
@@ -245,7 +243,7 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
- await click('.zap-modal-actions .btn-outline');
+ await click('.edit-actions .btn-outline');
assert.ok(closed, 'onClose was called from Cancel button');
});
@@ -268,7 +266,7 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
assert.notOk(closed, 'onClose was not called when clicking inside modal');
});
- test('it does not close when clicking the overlay background', async function (assert) {
+ test('it closes when clicking the overlay background', async function (assert) {
let closed = false;
this.handleClose = () => {
closed = true;
@@ -281,8 +279,8 @@ module('Integration | Component | zap-photo-modal', function (hooks) {
);
- await click('.zap-photo-modal-overlay');
+ await click('.modal-overlay');
- assert.notOk(closed, 'onClose was not called when clicking overlay');
+ assert.ok(closed, 'onClose was called when clicking overlay');
});
});