Compare commits

..
1 Commits
Author SHA1 Message Date
raucao 1c7720b689 Add hint to empty contributions list
CI / Lint (pull_request) Failing after 59s
CI / Test (pull_request) Successful in 1m13s
2026-08-19 11:52:50 -06:00
14 changed files with 49 additions and 152 deletions
+38 -77
View File
@@ -1,86 +1,47 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { on } from '@ember/modifier';
import Icon from './icon';
import ContributionPhoto from './contribution-photo';
import Modal from './modal';
import NostrConnect from './nostr-connect';
import eq from 'ember-truth-helpers/helpers/eq';
import not from 'ember-truth-helpers/helpers/not';
import restoreScroll from '../modifiers/restore-scroll';
export default class ContributionsTimelineComponent extends Component {
@tracked isNostrConnectModalOpen = false;
@action
openNostrConnectModal(event) {
event.preventDefault();
this.isNostrConnectModalOpen = true;
}
@action
closeNostrConnectModal() {
this.isNostrConnectModalOpen = false;
}
@action
onNostrConnected() {
this.closeNostrConnectModal();
this.args.onNostrConnected?.();
}
<template>
<div class="sidebar">
<div class="sidebar-header has-back-btn">
<button type="button" class="back-btn" {{on "click" @onBack}}>
<Icon @name="arrow-left" @size={{20}} @color="#333" />
</button>
<h2 class="sidebar-header-text-centered">
<span class="sidebar-header-icon-wrapper">
<Icon @name="activity" @size={{20}} @color="#898989" />
</span>
My Contributions
</h2>
<button type="button" class="close-btn" {{on "click" @onClose}}>
<Icon @name="x" @size={{20}} @color="#333" />
</button>
</div>
<div class="sidebar-content" {{restoreScroll @scrollTop}}>
{{#if @isLoading}}
<div class="sidebar-loading">
<Icon @name="loading-ring" @size={{24}} @color="#898989" />
</div>
{{else if (not @isConnected)}}
<p class="empty-state">
<a
href="#"
role="button"
tabindex="0"
{{on "click" this.openNostrConnectModal}}
>Connect your Nostr account</a>
to see your contributions.
</p>
{{else if (not @items.length)}}
<p class="empty-state">No contributions yet. Start by adding photos to
places.</p>
{{else}}
<ul class="contributions-list">
{{#each @items as |item|}}
{{#if (eq item.type "photo")}}
<ContributionPhoto @item={{item}} @onSelect={{@onSelect}} />
{{/if}}
{{/each}}
</ul>
{{/if}}
</div>
<template>
<div class="sidebar">
<div class="sidebar-header has-back-btn">
<button type="button" class="back-btn" {{on "click" @onBack}}>
<Icon @name="arrow-left" @size={{20}} @color="#333" />
</button>
<h2 class="sidebar-header-text-centered">
<span class="sidebar-header-icon-wrapper">
<Icon @name="activity" @size={{20}} @color="#898989" />
</span>
My Contributions
</h2>
<button type="button" class="close-btn" {{on "click" @onClose}}>
<Icon @name="x" @size={{20}} @color="#333" />
</button>
</div>
{{#if this.isNostrConnectModalOpen}}
<Modal @onClose={{this.closeNostrConnectModal}}>
<NostrConnect @onConnect={{this.onNostrConnected}} />
</Modal>
{{/if}}
</template>
}
<div class="sidebar-content" {{restoreScroll @scrollTop}}>
{{#if @isLoading}}
<div class="sidebar-loading">
<Icon @name="loading-ring" @size={{24}} @color="#898989" />
</div>
{{else if (not @isConnected)}}
<p class="empty-state">
Connect your Nostr account to see your contributions.
</p>
{{else if (not @items.length)}}
<p class="empty-state">No contributions yet. Start by adding photos to places.</p>
{{else}}
<ul class="contributions-list">
{{#each @items as |item|}}
{{#if (eq item.type "photo")}}
<ContributionPhoto @item={{item}} @onSelect={{@onSelect}} />
{{/if}}
{{/each}}
</ul>
{{/if}}
</div>
</div>
</template>
+1 -1
View File
@@ -232,7 +232,7 @@ export default class PlaceDetails extends Component {
return htmlSafe(
parts
.map((p) => {
const safeTel = p.replace(/[\s()+.-]/g, '');
const safeTel = p.replace(/[\s-]+/g, '');
return `<a href="https://wa.me/${safeTel}" target="_blank" rel="noopener noreferrer">${p}</a>`;
})
.join('<br>')
-5
View File
@@ -43,11 +43,6 @@ export default class ContributionsController extends Controller {
this.router.transitionTo(`/place/${item.placeIdentifier}`);
}
@action
onNostrConnected() {
this.loadContributionsTask.perform(this.nostrAuth.pubkey);
}
@action
backToMenu() {
this.router.transitionTo('menu');
-1
View File
@@ -10,7 +10,6 @@ import ContributionsTimeline from '#components/contributions-timeline';
@onSelect={{@controller.selectContribution}}
@onBack={{@controller.backToMenu}}
@onClose={{@controller.close}}
@onNostrConnected={{@controller.onNostrConnected}}
/>
{{/if}}
</template>
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "marco",
"version": "1.27.1",
"version": "1.27.0",
"private": true,
"description": "Unhosted maps app",
"repository": {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -39,8 +39,8 @@
<meta name="msapplication-TileColor" content="#F6E9A6">
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
<script type="module" crossorigin src="/assets/main-D3dQ-CGf.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-P9lvOtv_.css">
<script type="module" crossorigin src="/assets/main-MT6QR2E9.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-sIiovt6q.css">
</head>
<body>
<div id="modal-portal"></div>
@@ -1,23 +1,14 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'marco/tests/helpers';
import { render, click } from '@ember/test-helpers';
import Service from '@ember/service';
import ContributionsTimeline from 'marco/components/contributions-timeline';
import { setupNostrMocks } from 'marco/tests/helpers/mock-nostr';
function noop() {}
class MockToastService extends Service {
show() {}
}
module('Integration | Component | contributions-timeline', function (hooks) {
setupRenderingTest(hooks);
setupNostrMocks(hooks);
hooks.beforeEach(function () {
this.owner.register('service:toast', MockToastService);
this.noop = noop;
this.emptyItems = [];
});
@@ -57,28 +48,6 @@ module('Integration | Component | contributions-timeline', function (hooks) {
assert.dom('.empty-state').includesText('Connect your Nostr account');
});
test('clicking "Connect your Nostr account" opens the Nostr connect modal', async function (assert) {
await render(
<template>
<div id="modal-portal"></div>
<ContributionsTimeline
@items={{this.emptyItems}}
@isLoading={{false}}
@isConnected={{false}}
@onBack={{this.noop}}
@onClose={{this.noop}}
@onSelect={{this.noop}}
/>
</template>
);
assert.dom('.nostr-connect-modal').doesNotExist();
await click('.empty-state a');
assert.dom('.nostr-connect-modal').exists();
});
test('it renders an empty state when connected but no contributions', async function (assert) {
await render(
<template>
@@ -320,14 +320,14 @@ module('Integration | Component | place-details', function (hooks) {
const links = whatsappBlock.querySelectorAll('a[href^="https://wa.me/"]');
assert.strictEqual(links.length, 2, 'Rendered exactly 2 WhatsApp links');
// Verify it stripped the dashes, spaces and leading plus for the wa.me URL
// Verify it stripped the dashes and spaces for the wa.me URL
assert.strictEqual(
links[0].getAttribute('href'),
'https://wa.me/44987654321'
'https://wa.me/+44987654321'
);
assert.strictEqual(
links[1].getAttribute('href'),
'https://wa.me/12345678900'
'https://wa.me/+12345678900'
);
// Verify it kept the dashes and spaces for the visible text
@@ -335,33 +335,6 @@ module('Integration | Component | place-details', function (hooks) {
assert.dom(links[1]).hasText('+1 234-567 8900');
});
test('it strips parentheses, dots and the leading plus from whatsapp hrefs', async function (assert) {
const place = {
title: 'Chat Shop',
osmTags: {
whatsapp: '+504-9850-3802;(504) 9850.3802;+1.234.567.8900',
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const links = this.element.querySelectorAll('a[href^="https://wa.me/"]');
assert.strictEqual(links.length, 3, 'Rendered exactly 3 WhatsApp links');
assert.strictEqual(
links[0].getAttribute('href'),
'https://wa.me/50498503802'
);
assert.strictEqual(
links[1].getAttribute('href'),
'https://wa.me/50498503802'
);
assert.strictEqual(
links[2].getAttribute('href'),
'https://wa.me/12345678900'
);
});
test('it renders correct OpenStreetMap link for an OSM place', async function (assert) {
const place = {
title: 'OSM Place',