Compare commits
5 Commits
54e2766dc4
...
v1.17.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
2b219fe0cf
|
|||
|
9fd6c4d64d
|
|||
|
8e5b2c7439
|
|||
|
0f29430e1a
|
|||
|
0059d89cc3
|
@@ -0,0 +1,14 @@
|
|||||||
|
import Component from '@glimmer/component';
|
||||||
|
import { service } from '@ember/service';
|
||||||
|
|
||||||
|
export default class ToastComponent extends Component {
|
||||||
|
@service toast;
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{#if this.toast.isVisible}}
|
||||||
|
<div class="toast-notification">
|
||||||
|
{{this.toast.message}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</template>
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ export default class SearchRoute extends Route {
|
|||||||
@service mapUi;
|
@service mapUi;
|
||||||
@service storage;
|
@service storage;
|
||||||
@service router;
|
@service router;
|
||||||
|
@service toast;
|
||||||
|
|
||||||
queryParams = {
|
queryParams = {
|
||||||
lat: { refreshModel: true },
|
lat: { refreshModel: true },
|
||||||
@@ -199,8 +200,12 @@ export default class SearchRoute extends Route {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
error() {
|
error(error, transition) {
|
||||||
this.mapUi.stopSearch();
|
this.mapUi.stopSearch();
|
||||||
return true; // Bubble error
|
this.toast.show('Search request failed. Please try again.');
|
||||||
|
if (transition) {
|
||||||
|
transition.abort();
|
||||||
|
}
|
||||||
|
return false; // Prevent bubble and stop transition
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -155,7 +155,7 @@ out center;
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
console.error('Category search failed', e);
|
console.error('Category search failed', e);
|
||||||
return [];
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import Service from '@ember/service';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
|
export default class ToastService extends Service {
|
||||||
|
@tracked message = null;
|
||||||
|
@tracked isVisible = false;
|
||||||
|
timeoutId = null;
|
||||||
|
|
||||||
|
show(message, duration = 3000) {
|
||||||
|
this.message = message;
|
||||||
|
this.isVisible = true;
|
||||||
|
|
||||||
|
if (this.timeoutId) {
|
||||||
|
clearTimeout(this.timeoutId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timeoutId = setTimeout(() => {
|
||||||
|
this.isVisible = false;
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1321,3 +1321,36 @@ button.create-place {
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Toast Notification */
|
||||||
|
.toast-notification {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background-color: rgb(51 51 51 / 85%);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
color: white;
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
z-index: 9999;
|
||||||
|
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
|
||||||
|
animation: fade-in-up 0.3s ease-out forwards;
|
||||||
|
text-align: center;
|
||||||
|
max-width: 90%;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in-up {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, 1rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { pageTitle } from 'ember-page-title';
|
|||||||
import Map from '#components/map';
|
import Map from '#components/map';
|
||||||
import AppHeader from '#components/app-header';
|
import AppHeader from '#components/app-header';
|
||||||
import AppMenu from '#components/app-menu/index';
|
import AppMenu from '#components/app-menu/index';
|
||||||
|
import Toast from '#components/toast';
|
||||||
import { service } from '@ember/service';
|
import { service } from '@ember/service';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
@@ -89,6 +90,8 @@ export default class ApplicationComponent extends Component {
|
|||||||
<AppMenu @onClose={{this.closeAppMenu}} />
|
<AppMenu @onClose={{this.closeAppMenu}} />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<Toast />
|
||||||
|
|
||||||
{{outlet}}
|
{{outlet}}
|
||||||
</template>
|
</template>
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "marco",
|
"name": "marco",
|
||||||
"version": "1.16.0",
|
"version": "1.17.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Unhosted maps app",
|
"description": "Unhosted maps app",
|
||||||
"repository": {
|
"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
+2
-2
@@ -39,8 +39,8 @@
|
|||||||
<meta name="msapplication-TileColor" content="#F6E9A6">
|
<meta name="msapplication-TileColor" content="#F6E9A6">
|
||||||
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
|
<meta name="msapplication-TileImage" content="/icons/icon-144.png">
|
||||||
|
|
||||||
<script type="module" crossorigin src="/assets/main-C4F17h3W.js"></script>
|
<script type="module" crossorigin src="/assets/main-DM7YMuyX.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/main-CKp1bFPU.css">
|
<link rel="stylesheet" crossorigin href="/assets/main-OLSOzTKA.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -218,4 +218,56 @@ module('Acceptance | search', function (hooks) {
|
|||||||
// Ensure it shows "Results" not "Nearby"
|
// Ensure it shows "Results" not "Nearby"
|
||||||
assert.dom('.sidebar-header h2').includesText('Results');
|
assert.dom('.sidebar-header h2').includesText('Results');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('search error handling prevents opening empty panel and shows toast', async function (assert) {
|
||||||
|
// Mock Osm Service to throw an error
|
||||||
|
class MockOsmService extends Service {
|
||||||
|
async getCategoryPois() {
|
||||||
|
throw new Error('Overpass request failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.owner.register('service:osm', MockOsmService);
|
||||||
|
|
||||||
|
class MockStorageService extends Service {
|
||||||
|
savedPlaces = [];
|
||||||
|
findPlaceById() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
isPlaceSaved() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rs = { on: () => {} };
|
||||||
|
placesInView = [];
|
||||||
|
loadPlacesInBounds() {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.owner.register('service:storage', MockStorageService);
|
||||||
|
|
||||||
|
class MockMapService extends Service {
|
||||||
|
getBounds() {
|
||||||
|
return {
|
||||||
|
minLat: 52.5,
|
||||||
|
minLon: 13.4,
|
||||||
|
maxLat: 52.6,
|
||||||
|
maxLon: 13.5,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.owner.register('service:map', MockMapService);
|
||||||
|
|
||||||
|
await visit('/');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await visit('/search?category=coffee&lat=52.52&lon=13.405');
|
||||||
|
} catch {
|
||||||
|
// Aborted transition throws, which is expected
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.dom('.toast-notification').exists('Toast should be visible');
|
||||||
|
assert
|
||||||
|
.dom('.toast-notification')
|
||||||
|
.hasText('Search request failed. Please try again.');
|
||||||
|
assert.dom('.places-sidebar').doesNotExist('Results panel should not open');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user