How to implement fallback page with Ionic 4 router (and without Angular) - ionic-framework

I am using Ionic 4 and Stenciljs for a progressive web app (PWA) and I am not also using Angular. I am using ion-router for the routing. Can anybody help me understand how to implement a fallback "Page Not Found" page to display when someone enters a URL for which there is no defined route (i.e. a page that does not exist)? All the examples I can find assume the use of Angular, but I'm trying to see if I get where I need with just standard web components and no additional framework.
Following is how some of my current router code looks (it makes use of the tabs component).
return (
<ion-router useHash={false}>
<ion-route-redirect from="/" to='/blog' />
<ion-route component="page-tabs">
<ion-route url="/blog" component="tab-blog">
<ion-route component="page-blog"></ion-route>
</ion-route>
<ion-route url="/books" component="tab-books">
<ion-route component="page-books"></ion-route>
</ion-route>
<ion-route url="/art" component="tab-art">
<ion-route component="page-art"></ion-route>
</ion-route>
<ion-route url="/about" component="tab-about">
<ion-route component="page-about"></ion-route>
</ion-route>
<ion-route url="/beaver-cage-command-chron" component="tab-books">
<ion-route component="page-cmd-chron-beaver-cage"></ion-route>
</ion-route>
<ion-route url="/cage" component="tab-books">
<ion-route component="page-cage"></ion-route>
</ion-route>
<ion-route url="/d-1-3-weapons-platoon" component="tab-books">
<ion-route component="page-photos-weapons-platoon"></ion-route>
</ion-route>
<ion-route url="/ray-kelley-silver-star" component="tab-books">
<ion-route component="page-ray-kelley-silver-star"></ion-route>
</ion-route>
<ion-route url="/photos" component="tab-books">
<ion-route url="/:name" component="app-photos"></ion-route>
</ion-route>
<ion-route url="/vietnam-1967-amphibious-combat" component="tab-books">
<ion-route component="page-vietnam-1967-amphibious-combat"></ion-route>
</ion-route>
{blogPostRoutes}
</ion-route>
</ion-router>
);
}

The only way I know of that this is possible is using multiple route redirects:
<ion-router>
<ion-route url="/one" component="page-one" />
<ion-route url="/two" component="page-two" />
<ion-route url="/three" component="page-three" />
<ion-route url="/404" component="page-404" />
<ion-route-redirect from="/one" to="/one" />
<ion-route-redirect from="/two" to="/two" />
<ion-route-redirect from="/three" to="/three" />
<ion-route-redirect from="*" to="/404" />
</ion-router>
That is because only the first matching route-redirect will be called. However this becomes quite unfeasible for a large number of routes. A feature request for adding a fallback route is being tracked on Ionic's Github Repo.
Edit: I actually just had the idea of wrapping this in a functional component to avoid having so much duplication:
import { FunctionalComponent } from '#stencil/core';
import { JSX } from '#ionic/core';
export const Route: FunctionalComponent<JSX.IonRoute> = props => [
<ion-route {...props} />,
<ion-route-redirect from={props.url} to={props.url} />,
];
import { Route } from './route';
<ion-router>
<Route url="/one" component="page-one" />
<Route url="/two" component="page-two" />
<Route url="/three" component="page-three" />
<ion-route-redirect from="*" to="/404" />
</ion-router>

You can add a route for fallback page at the end of the router like so
const routes: Route = [
{path: "OtherPaths",component: OtherPathsComponent},
{path: "PathNotFound", component: PathNotFoundComponent},
{path: "**", redirectTo: "PathNotFound"}
]

Related

Is there a way to limit framer-motion touch slide area?

I implemented drag and drop sorting through framer-motion and react-dnd.
The operation is executed normally, but since the framer area is held as the parent element (parent) of the MultipleSelectionInput component, dragging is also executed to the input area.
But I would like to implement so that the slide is only possible via the arrow on the left called SlideArrow.
Is there any way to limit the slide area in the framer area?
The structure of the component is shown in the following picture.
The blue InputAndNextContainer component is a wrapper component for separation of concerns and has no function.
//MultipleSelection.tsx
<DndProvider backend={TouchBackend}>
<Reorder.Group values={questionItemIdList} onReorder={setQuestionItemIdList} css={ulStyle}>
{questionItemIdList.map((id, idx, arr) => (
<Reorder.Item key={id} value={id}>
<InputAndNextPartContainer
...
moveCard={onMove}
/>
</Reorder.Item>
))}
</Reorder.Group>
</DndProvider>
//InputAndNextPartContainer.tsx
<Draggable handleMove={moveCard} index={selectionNumber - 1} id={id}>
<MultipleSelectionInput
...
moveCard={moveCard}
/>
</Draggable>
//MultipleSelectionInput.tsx
<MultipleSelectionLi>
<SlideArrow />
<HaveNextPart>
<input
placeholder="선택지 입력"
defaultValue={inputContent.content}
type="text"
name="multiple-select-input"
onChange={(e) => onChangeHandler(e)}
/>
{hasDeleteBtn ? <CloseCircle className="close-svg" onClick={onRemove} /> : null}
<div onClick={() => setVisibleModal(true)} className="nexts">
<strong>다음 파트</strong>로 진행하기
<Arrow fill={Common.colors.GY700} />
</div>
</HaveNextPart>
</MultipleSelectionLi>
Below are my dependencies.
"dependencies": {
"#emotion/react": "^11.10.0",
"#emotion/styled": "^11.10.0",
"#tanstack/react-query": "^4.0.10",
"#types/lodash": "^4.14.185",
"#types/recoil": "^0.0.9",
"axios": "^0.27.2",
"cookie": "^0.5.0",
"dnd-core": "^16.0.1",
"framer-motion": "^7.5.2",
"graphql": "^16.5.0",
"graphql-request": "^4.3.0",
"graphql-tag": "^2.12.6",
"immutability-helper": "^3.1.1",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"next": "12.2.3",
"react": "18.2.0",
"react-dnd": "^16.0.1",
"react-dnd-touch-backend": "^16.0.1",
"react-dom": "18.2.0",
"react-query": "^3.39.1",
"react-spinners": "^0.13.4",
"recoil": "^0.7.4"
},

How to connect Phantom wallet to a Flutter app using deep links?

I'm making a flutter mobile app where I want to connect the user to the Phantom wallet using the connect deep link and then set the redirect_link as a Firebase dynamic link for the app, however I am not getting a response from the Phantom wallet as a query parameters. Any help will be highly appreciated! Thanks.
Install uni_links and url_luncher pakage
add this intent to androidManifest
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<!-- Accepts URIs that begin with https://YOUR_HOST -->
<data
android:scheme="https"
android:host="[YOUR_HOST]" />
</intent-filter>
then create queryParameter like
Map<String, dynamic> queryParameters = {
"dapp_encryption_public_key":
base58.encode(Uint8List.fromList(keypair.publicKey)),
"cluster": "devnet",
"app_url": "https://google.com",
"redirect_link":
"app://flutterdapp?handleQuery=onConnect}",
};
then lunchUrl
final url =Uri(
scheme: "https",
host: "phantom.app",
path: "/ul/v1/onConnect",
queryParameters: queryParameters,
);
launchUrl(
url,
mode: LaunchMode.externalNonBrowserApplication,
);
and recive data from phantom like
StreamSubscription _sub;
Future<void> initUniLinks() async {
// ... check initialLink
// Attach a listener to the stream
_sub = linkStream.listen((String? link) {
// Parse the link and warn the user, if it is not correct
}, onError: (err) {
// Handle exception by warning the user their action did not succeed
});
// NOTE: Don't forget to call _sub.cancel() in dispose()
}
// ...
hope help you

Flutter audio_service stream update notification with IcyMetadata

I want to to a streamApp for just one stream/url.
In the audio_service example there is an MediaItem added in the AudioPlayerHandler.
This works so fahr but when IcyMedata updated the notification … obviously … has title and stuff from the added MediaItem. In the app i can update per _audioHandler.playbackstate and _player.icyMetadata!.info!.title! and some sting.splits(' - ') I can update Infos in the app with StreamBilder.
Also tried a dirty hack to add title in the PlaybackState object by changing the audio_service and add a title as property and connect it in the custom AudioHandler with _player.icy … .
But there must be a proper way to set metadata new or by this framework by itself. _audioHandler.updateMediaItem does nothing so far to the notification info, this is what i need!
I found a addStream … my try (Snippets)
Stream<MediaItem> _item() async* { MediaItem(
id: "http://stream.drumandbass.fm:9012",
title: "TEST",
artist: "Hello",
album: "Album",
duration: const Duration(milliseconds: 5739820),
genre: 'Drum and Bass',
artUri: Uri.parse('test.jpg'));
}
Future<AudioPlayerHandler> initAH() async {
return await AudioService.init(
builder: () => AudioPlayerHandler(),
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.ryanheise.myapp.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
),
);
class AudioPlayerHandler extends BaseAudioHandler {
/// Initialise our audio handler.
AudioPlayerHandler() {
// So that our clients (the Flutter UI and the system notification) know
// what state to display, here we set up our audio handler to broadcast all
// playback state changes as they happen via playbackState...
_player.playbackEventStream.map(_transformEvent).pipe(playbackState);
// ... and also the current media item via mediaItem.
mediaItem.addStream(_item());
// Load the player.
_player.setAudioSource(AudioSource.uri(Uri.parse(mediaItem.value!.id)));
}
// some play stop the rest of the example of audio_service
Widget:
FutureBuilder<AudioPlayerHandler>(
future: initAH(),
builder: (BuildContext context,
AsyncSnapshot<AudioPlayerHandler> snapshot) {
if (!snapshot.hasData) {
// while data is loading:
return Center(
child: CircularProgressIndicator(),
);
} else {
_audioHandler = snapshot.data!;
loaded = true;
// data loaded:
return Container();
}
},
),
… with _player.add(MediaItem()); like in example i get a snapshot.hasData but with addStream loaded keeps false.
I thankful for any tips to handle this issue properly.
thx,
Tom
As you not seeing the notification. please check your android configuration.
<manifest xmlns:tools="http://schemas.android.com/tools" ...>
<!-- ADD THESE TWO PERMISSIONS -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application ...>
...
<!-- EDIT THE android:name ATTRIBUTE IN YOUR EXISTING "ACTIVITY" ELEMENT -->
<activity android:name="com.ryanheise.audioservice.AudioServiceActivity" ...>
...
</activity>
<!-- ADD THIS "SERVICE" element -->
<service android:name="com.ryanheise.audioservice.AudioService"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
<!-- ADD THIS "RECEIVER" element -->
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true" tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>
if you are update you metadata you have just added updatemetadata of your view dart file otherwise set in initstate
like below
before you Add udioHandler.updateMediaItem() you need to update your Audiohandler file
enter image description here
enter image description here
its work for me

How to pass data between pages without URL Parameters

I'm wondering how I can pass non-string data between two pages in Ionic 5 using ReactRouter.
All solutions I could find used Ionic and Angular or just passed one string as URL parameter.
These are my components so far:
App.tsx
const App: React.FC = () => {
return (
<IonApp>
<IonReactRouter>
<IonSplitPane contentId="main">
<Menu />
<IonRouterOutlet id="main">
<Route path="/page/Home" component={Home} exact />
<Route path="/page/DataEntry" component={DataEntry} exact />
<Route path="/page/Request" component={Request} exact />
<Route path="/page/ResultMap" component={ResultMap} exact />
<Redirect from="/" to="/page/Home" exact />
</IonRouterOutlet>
</IonSplitPane>
</IonReactRouter>
</IonApp>
);
};
Page 1 here collects user input data (strings, objects, arrays) and I want to call the route '/page/ResultMap' on Button click and pass the data, so the next page can handle it:
<IonGrid>
<IonRow>
<IonCol class="ion-text-center">
<IonButton text-center="ion-text-center" color="primary" size="default" routerLink='/page/ResultMap'>Erkunden!</IonButton>
</IonCol>
</IonRow>
</IonGrid>
Page 2, which should receive the Data:
const ResultMap: React.FC = () => {
return (
<IonPage>
<IonHeader>
<IonToolbar>
<IonButtons slot="start">
<IonMenuButton />
</IonButtons>
<IonTitle>Map</IonTitle>
</IonToolbar>
</IonHeader>
<IonContent fullscreen>
</IonContent>
</IonPage>
);
};
I understand the React principle about props and state, I just dont know how to combine it with Ionic in this case.
I appreciate your help!
Edit:
As suggested I changed the button onClick like this:
<IonButton text-center="ion-text-center" color="primary" size="default" onClick={e => {
e.preventDefault();
history.push({
pathname: '/page/ResultMap',
state: { time: transportTime }
})}}>
And try to receive the data on the ResultMap page like this:
let time = history.location.state.time;
But I get the error:
Object is of type 'unknown'. TS2571
7 | let history = useHistory();
8 |
> 9 | let time = history.location.state.time;
| ^
How do I access the passed object on the new page?
as for react-router I know you can use this:
history.push({
pathname: '/template',
state: { detail: response.data }
})
in this situation you can pass data without URL Params
can also use history.replace
if you redirect and want the back button work properly to the end user
and for the history do the following
let history = useHistory();
Check this link for a great understand how to implement the useHistory type

React-router redirect is looping over components in Meteorjs

I am experimenting on custom admin view page and do have one question about the behavior.
The current logic at the moment is:
If the user in not authenticated and tries to visit /admin-panel or its children he/she gets redirected to /& login page.
If user in logged in and visits /& (login page) he/she gets redirected to /admin-panel
The problem is when the user is lodded in and is on /admin-panel and reloads the page the following occurs:
First /& page loads
Then /& redirects user to /admin-panel
If the user was on or /admin-panel/child_component after reload he/she will be on /admin-panel and will have to navigate again to /child_component
Can you please explain what is the cause of current behavior and if there is some way to make user stay on the page the reload was initiated and can the constant redirection be avoided?
The login page /&
import React, { Component } from 'react';
import { Link, browserHistory } from 'react-router';
import { Tracker } from 'meteor/tracker'
class Backdoor extends Component {
onSubmit(event) {
event.preventDefault();
// Collecting user input
const self = this;
const email = $(event.target).find('[name=email]').val();
const password = $(event.target).find('[name=password]').val();
Meteor.loginWithPassword(email, password, function (err) {
browserHistory.push('admin-panel');
});
}
componentWillMount(){
Tracker.autorun(() => {
if (Meteor.user()) {
browserHistory.push('/admin-panel')
} else if(!Meteor.user()) {
browserHistory.push('/&')
}
})
}
render() {
return (
// Login form
);
}
}
export default Backdoor;
React-router paths':
const routes = (
<Router history={browserHistory}>
<Route path='/' component={App}>
<Router path='about' component={About} />
</Route>
<Route path='&' component={Backdoor} />
<Route path='admin' component={AdminPanel}>
<Router path='/admin/admin_component' component={AdminChild} />
</Route>
</Router>
I would change your routes file in the following way:
const routes = (
<Router history={browserHistory}>
<Route path='/' component={App}>
<Route path='about' component={About} />
<Route path='&' component={Backdoor} />
<Route path='admin' component={Admin} />
<Route path='admin/admin_component' component={AdminChild} />
<Route path='admin/admin_panel' component={AdminPanel} />
</Route>
</Router>
);
So we got rid of the nested "Router" components that you had in there and added a "AdminPanel" component.
One of the things I usually do is assign an IndexRoute to my apps. You can look that up and it might provides some benefit, I didn't add it in since you didn't have it in your code.
Another efficiency (IMO) is to nest your admin routes as such:
const routes = (
<Router history={browserHistory}>
<Route path='/' component={App}>
<Router path='about' component={About} />
<Route path='&' component={Backdoor} />
<Route path='admin' component={Admin}>
<Route path='/admin_component' component={AdminChild} />
<Route path='/admin_panel' component={AdminPanel} />
</Route>
</Route>
</Router>
);
Note that the admin panel is nested in the admin route so to get there it would be /admin/admin_panel. Also, I would use "" quotes instead of ''.
First of all I've got rid of /& route and now the Admin Authentication routes look like that:
const newRoutes = (
<Router history={browserHistory}>
<Route path='/' component={App}>
<Router path='about' component={About} />
</Route>
<Route path='/admin' component={Admin} >
<Route path='/admin/admin_child' component={AdminChild} />\
// Feel free to add more routes here
</Route>
</Router>
);
So inside the admin route you define if statement for checking if user is logged in. The hole component looks like that:
export class Admin extends Component {
componentWillMount(){
Tracker.autorun(() => {
if(!Meteor.userId()) {
browserHistory.push('/admin')
}
})
}
render(props) {
if (Meteor.userId()){
return (<div><JuliaNav />{this.props.children}</div>)
} else {
return (<div><Backdoor /></div>)
}
}
}
So if the admin is not logged in React returns log-in form, and if he/she is logged in the component returns AdminNavigation for further interaction with data.
Tracker checks the state of user and if the button Log Off (inside AdminNavigation) will be clicked the page will be reloaded (with browserHistory.push) and AdminNavigation component will be replaced with the log-in form.