Compare commits
5 Commits
54e2766dc4
...
v1.17.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
2b219fe0cf
|
|||
|
9fd6c4d64d
|
|||
|
8e5b2c7439
|
|||
|
0f29430e1a
|
|||
|
0059d89cc3
|
14
app/components/toast.gjs
Normal file
14
app/components/toast.gjs
Normal file
@@ -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 storage;
|
||||
@service router;
|
||||
@service toast;
|
||||
|
||||
queryParams = {
|
||||
lat: { refreshModel: true },
|
||||
@@ -199,8 +200,12 @@ export default class SearchRoute extends Route {
|
||||
}
|
||||
|
||||
@action
|
||||
error() {
|
||||
error(error, transition) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ out center;
|
||||
return [];
|
||||
}
|
||||
console.error('Category search failed', e);
|
||||
return [];
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
app/services/toast.js
Normal file
21
app/services/toast.js
Normal file
@@ -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;
|
||||
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 AppHeader from '#components/app-header';
|
||||
import AppMenu from '#components/app-menu/index';
|
||||
import Toast from '#components/toast';
|
||||
import { service } from '@ember/service';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
@@ -89,6 +90,8 @@ export default class ApplicationComponent extends Component {
|
||||
<AppMenu @onClose={{this.closeAppMenu}} />
|
||||
{{/if}}
|
||||
|
||||
<Toast />
|
||||
|
||||
{{outlet}}
|
||||
</template>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "marco",
|
||||
"version": "1.16.0",
|
||||
"version": "1.17.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
2
release/assets/main-DM7YMuyX.js
Normal file
2
release/assets/main-DM7YMuyX.js
Normal file
File diff suppressed because one or more lines are too long
1
release/assets/main-OLSOzTKA.css
Normal file
1
release/assets/main-OLSOzTKA.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -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-C4F17h3W.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-CKp1bFPU.css">
|
||||
<script type="module" crossorigin src="/assets/main-DM7YMuyX.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/main-OLSOzTKA.css">
|
||||
</head>
|
||||
<body>
|
||||
</body>
|
||||
|
||||
@@ -218,4 +218,56 @@ module('Acceptance | search', function (hooks) {
|
||||
// Ensure it shows "Results" not "Nearby"
|
||||
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