Merge pull request 'Add mobile numbers and WhatsApp links to place details' (#41) from feature/place_details into master
All checks were successful
CI / Lint (push) Successful in 28s
CI / Test (push) Successful in 44s

Reviewed-on: #41
This commit was merged in pull request #41.
This commit is contained in:
2026-04-12 12:15:25 +00:00
4 changed files with 141 additions and 7 deletions

View File

@@ -130,15 +130,24 @@ export default class PlaceDetails extends Component {
formatMultiLine(val, type) {
if (!val) return null;
const parts = val
.split(';')
.map((s) => s.trim())
.filter(Boolean);
const parts = [
...new Set(
val
.split(';')
.map((s) => s.trim())
.filter(Boolean)
),
];
if (parts.length === 0) return null;
if (type === 'phone') {
return htmlSafe(
parts.map((p) => `<a href="tel:${p}">${p}</a>`).join('<br>')
parts
.map((p) => {
const safeTel = p.replace(/[\s-]+/g, '');
return `<a href="tel:${safeTel}">${p}</a>`;
})
.join('<br>')
);
}
@@ -148,6 +157,17 @@ export default class PlaceDetails extends Component {
);
}
if (type === 'whatsapp') {
return htmlSafe(
parts
.map((p) => {
const safeTel = p.replace(/[\s-]+/g, '');
return `<a href="https://wa.me/${safeTel}" target="_blank" rel="noopener noreferrer">${p}</a>`;
})
.join('<br>')
);
}
if (type === 'url') {
return htmlSafe(
parts
@@ -165,8 +185,27 @@ export default class PlaceDetails extends Component {
}
get phone() {
const val = this.tags.phone || this.tags['contact:phone'];
return this.formatMultiLine(val, 'phone');
const rawValues = [
this.tags.phone,
this.tags['contact:phone'],
this.tags.mobile,
this.tags['contact:mobile'],
].filter(Boolean);
if (rawValues.length === 0) return null;
return this.formatMultiLine(rawValues.join(';'), 'phone');
}
get whatsapp() {
const rawValues = [
this.tags.whatsapp,
this.tags['contact:whatsapp'],
].filter(Boolean);
if (rawValues.length === 0) return null;
return this.formatMultiLine(rawValues.join(';'), 'whatsapp');
}
get email() {
@@ -343,6 +382,15 @@ export default class PlaceDetails extends Component {
</p>
{{/if}}
{{#if this.whatsapp}}
<p class="content-with-icon">
<Icon @name="whatsapp" @title="WhatsApp" />
<span>
{{this.whatsapp}}
</span>
</p>
{{/if}}
{{#if this.website}}
<p class="content-with-icon">
<Icon @name="globe" @title="Website" />

4
app/icons/whatsapp.svg Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="-1.66 0 740.82 740.82" xmlns="http://www.w3.org/2000/svg">
<path d="m630.06 107.66c-69.329-69.387-161.53-107.62-259.76-107.66-202.4 0-367.13 164.67-367.22 367.07-0.027 64.699 16.883 127.86 49.016 183.52l-52.095 190.23 194.67-51.047c53.634 29.244 114.02 44.656 175.48 44.682h0.151c202.38 0 367.13-164.69 367.21-367.09 0.039-98.088-38.121-190.32-107.45-259.71m-259.76 564.8h-0.125c-54.766-0.021-108.48-14.729-155.34-42.529l-11.146-6.613-115.52 30.293 30.834-112.59-7.258-11.543c-30.552-48.58-46.689-104.73-46.665-162.38 0.067-168.23 136.99-305.1 305.34-305.1 81.521 0.031 158.15 31.81 215.78 89.482s89.342 134.33 89.311 215.86c-0.07 168.24-136.99 305.12-305.21 305.12m167.42-228.51c-9.176-4.591-54.286-26.782-62.697-29.843-8.41-3.061-14.526-4.591-20.644 4.592-6.116 9.182-23.7 29.843-29.054 35.964-5.351 6.122-10.703 6.888-19.879 2.296-9.175-4.591-38.739-14.276-73.786-45.526-27.275-24.32-45.691-54.36-51.043-63.542-5.352-9.183-0.569-14.148 4.024-18.72 4.127-4.11 9.175-10.713 13.763-16.07 4.587-5.356 6.116-9.182 9.174-15.303 3.059-6.122 1.53-11.479-0.764-16.07s-20.643-49.739-28.29-68.104c-7.447-17.886-15.012-15.466-20.644-15.746-5.346-0.266-11.469-0.323-17.585-0.323-6.117 0-16.057 2.296-24.468 11.478-8.41 9.183-32.112 31.374-32.112 76.521s32.877 88.763 37.465 94.885c4.587 6.122 64.699 98.771 156.74 138.5 21.891 9.45 38.982 15.093 52.307 19.323 21.981 6.979 41.983 5.994 57.793 3.633 17.628-2.633 54.285-22.19 61.932-43.616 7.646-21.426 7.646-39.791 5.352-43.617-2.293-3.826-8.41-6.122-17.585-10.714" clip-rule="evenodd" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -110,6 +110,7 @@ import womensAndMensRestroomSymbol from '@waysidemapping/pinhead/dist/icons/wome
import loadingRing from '../icons/270-ring.svg?raw';
import nostrich from '../icons/nostrich-2.svg?raw';
import remotestorage from '../icons/remotestorage.svg?raw';
import whatsapp from '../icons/whatsapp.svg?raw';
import wikipedia from '../icons/wikipedia.svg?raw';
const ICONS = {
@@ -218,6 +219,7 @@ const ICONS = {
'village-buildings': villageBuildings,
'wall-hanging-with-mountains-and-sun': wallHangingWithMountainsAndSun,
'womens-and-mens-restroom-symbol': womensAndMensRestroomSymbol,
whatsapp,
wikipedia,
parking_p: parkingP,
car,
@@ -229,6 +231,7 @@ const ICONS = {
const FILLED_ICONS = [
'fork-and-knife',
'wikipedia',
'whatsapp',
'cup-and-saucer',
'coffee-bean',
'shopping-basket',

View File

@@ -255,4 +255,83 @@ module('Integration | Component | place-details', function (hooks) {
assert.dom('.actions button').hasText('Save');
assert.dom('.actions button').doesNotHaveClass('btn-secondary');
});
test('it aggregates phone and mobile tags without duplicates', async function (assert) {
const place = {
title: 'Phone Shop',
osmTags: {
phone: '+1-234-567-8900',
'contact:phone': '+1-234-567-8900; +1 000 000 0000',
mobile: '+1 987 654 3210',
'contact:mobile': '+1 987 654 3210',
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
// Use specific selector for the phone block since there's no cuisine or opening_hours
const metaInfos = Array.from(
this.element.querySelectorAll('.meta-info .content-with-icon')
);
const phoneBlock = metaInfos.find((el) => {
const iconSpan = el.querySelector('span.icon[title="Phone"]');
return !!iconSpan;
});
assert.ok(phoneBlock, 'Phone block is rendered');
const links = phoneBlock.querySelectorAll('a[href^="tel:"]');
assert.strictEqual(
links.length,
3,
'Rendered exactly 3 unique phone links'
);
assert.strictEqual(links[0].getAttribute('href'), 'tel:+12345678900');
assert.strictEqual(links[1].getAttribute('href'), 'tel:+10000000000');
assert.strictEqual(links[2].getAttribute('href'), 'tel:+19876543210');
assert.dom(links[0]).hasText('+1-234-567-8900');
assert.dom(links[1]).hasText('+1 000 000 0000');
assert.dom(links[2]).hasText('+1 987 654 3210');
});
test('it formats whatsapp tags into wa.me links', async function (assert) {
const place = {
title: 'Chat Shop',
osmTags: {
'contact:whatsapp': '+1 234-567 8900',
whatsapp: '+44 987 654 321', // Also tests multiple values
},
};
await render(<template><PlaceDetails @place={{place}} /></template>);
const metaInfos = Array.from(
this.element.querySelectorAll('.meta-info .content-with-icon')
);
const whatsappBlock = metaInfos.find((el) => {
const iconSpan = el.querySelector('span.icon[title="WhatsApp"]');
return !!iconSpan;
});
assert.ok(whatsappBlock, 'WhatsApp block is rendered');
const links = whatsappBlock.querySelectorAll('a[href^="https://wa.me/"]');
assert.strictEqual(links.length, 2, 'Rendered exactly 2 WhatsApp links');
// Verify it stripped the dashes and spaces for the wa.me URL
assert.strictEqual(
links[0].getAttribute('href'),
'https://wa.me/+44987654321'
);
assert.strictEqual(
links[1].getAttribute('href'),
'https://wa.me/+12345678900'
);
// Verify it kept the dashes and spaces for the visible text
assert.dom(links[0]).hasText('+44 987 654 321');
assert.dom(links[1]).hasText('+1 234-567 8900');
});
});