Building Membership based website with React - reactjs

I want to build a membership-based web app using React. The users would sign-up and pay before they can access exclusive content. If they have already registered or signed up they can just login and access the content.
How would I go about making such a web app with React? What would I need to implement?

You can use react-router, found in npm packages : https://www.npmjs.com/package/react-router
See this example to private routes https://reacttraining.com/react-router/web/example/auth-workflow
import React from "react";
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from "react-router-dom";
////////////////////////////////////////////////////////////
// 1. Click the public page
// 2. Click the protected page
// 3. Log in
// 4. Click the back button, note the URL each time
const AuthExample = () => (
<Router>
<div>
<AuthButton />
<ul>
<li>
<Link to="/public">Public Page</Link>
</li>
<li>
<Link to="/protected">Protected Page</Link>
</li>
</ul>
<Route path="/public" component={Public} />
<Route path="/login" component={Login} />
<PrivateRoute path="/protected" component={Protected} />
</div>
</Router>
);
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true;
setTimeout(cb, 100); // fake async
},
signout(cb) {
this.isAuthenticated = false;
setTimeout(cb, 100);
}
};
const AuthButton = withRouter(
({ history }) =>
fakeAuth.isAuthenticated ? (
<p>
Welcome!{" "}
<button
onClick={() => {
fakeAuth.signout(() => history.push("/"));
}}
>
Sign out
</button>
</p>
) : (
<p>You are not logged in.</p>
)
);
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
fakeAuth.isAuthenticated ? (
<Component {...props} />
) : (
<Redirect
to={{
pathname: "/login",
state: { from: props.location }
}}
/>
)
}
/>
);
const Public = () => <h3>Public</h3>;
const Protected = () => <h3>Protected</h3>;
class Login extends React.Component {
state = {
redirectToReferrer: false
};
login = () => {
fakeAuth.authenticate(() => {
this.setState({ redirectToReferrer: true });
});
};
render() {
const { from } = this.props.location.state || { from: { pathname: "/" } };
const { redirectToReferrer } = this.state;
if (redirectToReferrer) {
return <Redirect to={from} />;
}
return (
<div>
<p>You must log in to view the page at {from.pathname}</p>
<button onClick={this.login}>Log in</button>
</div>
);
}
}
export default AuthExample;

Related

React Router not redirecting after user was logged in

I have small problem with React Router and redirecting. After i have created a admin protected route, I want the user to be redirected to "user dashboard" after login. But my issue is that redirect is not working.
All is happening like this:
My Navigation componente, but this one i think is okay:
import React from 'react';
import { Link, withRouter } from 'react-router-dom';
import { signOut, isAuthUser } from '../../../utils/utils';
const isActive = (history, path) => {
if (history.location.pathname === path) {
return { color: '#ff9900' };
} else {
return { color: '#ffffff' };
}
};
const Navigation = ({ history }) => {
return (
<nav>
<ul className='nav nav-tabs bg-primary'>
<li className='nav-item'>
<Link className='nav-link' style={isActive(history, '/')} to='/'>
Home
</Link>
<Link
className='nav-link'
style={isActive(history, '/user/dashboard')}
to='/user/dashboard'
>
Dashboard
</Link>
{!isAuthUser() && (
<div>
<Link
className='nav-link'
style={isActive(history, '/signup')}
to='/signup'
>
Signup
</Link>
<Link
className='nav-link'
style={isActive(history, '/signin')}
to='/signin'
>
Signin
</Link>
</div>
)}
{isAuthUser() && (
<Link
className='nav-link'
style={isActive(history, '/signout')}
onClick={() => signOut()}
to='/'
>
Sign Out
</Link>
)}
</li>
</ul>
</nav>
);
};
export default withRouter(Navigation);
My app.js with Routes
import React from 'react';
import { BrowserRouter, Route, Switch } from 'react-router-dom';
import MainLayout from '../src/components/layout/MainLayout/MainLayout';
import Signup from './components/views/Signup/Signup';
import Signin from './components/views/Signin/Signin';
import Home from './components/views/Home/Home';
import PrivateRoute from './components/common/ProvateRoute/PrivateRoute';
import UserDashboard from './components/views/UserDashboard/UserDashboard';
function App() {
return (
<BrowserRouter>
<MainLayout>
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/signin' component={Signin} />
<Route exact path='/signup' component={Signup} />
<PrivateRoute exact path='/user/dashboard' component={UserDashboard} />
</Switch>
</MainLayout>
</BrowserRouter>
);
}
export default App;
My utils.js with some helper functions
export const signOut = () => {
if (typeof window !== 'undefined') {
localStorage.removeItem('jwt');
return fetch('http://localhost:8000/api/signout', { method: 'GET' }).then(res => {
console.log('signout', res);
});
}
};
export const authenticateUser = data => {
if (typeof window !== 'undefined') {
localStorage.setItem('jwt', JSON.stringify(data));
}
};
//check if user is auth and there is jwt item in localstorage. menu render
export const isAuthUser = () => {
if (typeof window == 'undefined') {
return false;
}
if (localStorage.getItem('jwt')) {
return JSON.parse(localStorage.getItem('jwt'));
} else {
return false;
}
};
So those looks okay in my opinion, but still i decided to post those here.
As all things that are most related are in my tow files: UserDashboard and Signin
My Signin.js looks like this:
import React, { Component } from 'react';
import { Redirect } from 'react-router-dom';
import axios from 'axios';
import Layout from '../../layout/Layout/Layout';
import { authenticateUser, isAuthUser } from '../../../utils/utils';
class Signin extends Component {
state = {
formData: {
email: '',
password: '',
},
userRedirect: false,
};
onChange = e => {
const { formData } = this.state;
//assign form data to new variable
let newFormData = { ...formData };
newFormData[e.target.name] = e.target.value;
this.setState({
formData: newFormData,
});
};
signIn = user => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
axios
.post('http://localhost:8000/api/signin', user, config)
.then(res => authenticateUser(res.data));
this.setState({
formData: { email: '', password: '' },
userRedirect: true,
});
};
onSubmit = e => {
const { password, email } = this.state.formData;
e.preventDefault();
this.signIn({ email, password });
};
signInForm = (email, password) => (
<form onSubmit={this.onSubmit}>
<div className='form-group'>
<label className='text-muted'>Email</label>
<input
type='email'
name='email'
value={email}
onChange={this.onChange}
className='form-control'
></input>
</div>
<div className='form-group'>
<label className='text-muted'>Password</label>
<input
type='password'
name='password'
minLength='6'
value={password}
onChange={this.onChange}
className='form-control'
></input>
</div>
<button className='btn btn-primary'>Submit</button>
</form>
);
redirecUser = () => {
const { userRedirect } = this.state;
const { user } = isAuthUser();
if (userRedirect === true) {
if (user && user.role === 1) {
return <Redirect to='/admin/dashboard' />;
} else {
return <Redirect to='/user/dashboard' />;
}
}
};
render() {
const { email, password } = this.state.formData;
return (
<Layout
title='Signin'
description='Login to your account'
className='container col-md-8 offset-md-2'
>
{this.signInForm(email, password)}
{this.redirecUser()}
</Layout>
);
}
}
export default Signin;
Here I am rendering all with signInForm and passing all data that i want with signIn(). From this i get user data: _id, email, password, role and token. This is sent to local storage.
Based on that what i get i want admin dashboard or user dashboard.
I have now olny user Dashboard
import React from 'react';
import { isAuthUser } from '../../../utils/utils';
import Layout from '../../layout/Layout/Layout';
const UserDashboard = () => {
const {
payload: {
user: { name, email, role },
},
} = isAuthUser();
return (
<Layout
title='User Dashboard'
description={`Wlecome ${name}`}
className='container col-md-8 offset-md-2'
>
<div className='card mb-5'>
<h3 className='card-header'>User information</h3>
<ul className='list-group'>
<li className='list-group-item'>{name}</li>
<li className='list-group-item'>{email}</li>
<li className='list-group-item'>
{role === 1 ? 'Admin' : 'Registered User'}
</li>
</ul>
</div>
<div className='card'>
<h3 className='card-header'>Purchase history</h3>
<ul className='list-group'>
<li className='list-group-item'>History</li>
</ul>
</div>
</Layout>
);
};
export default UserDashboard;
I have created PrivateRoute component based on documentation
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
import { isAuthUser } from '../../../utils/utils';
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route
{...rest}
render={props =>
isAuthUser() ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: '/signin', state: { from: props.location } }} />
)
}
/>
);
export default PrivateRoute;
I do get all data in local storage, but after signin user is not redirected
Thanks for any help
Since you are trying to redirect from a function in your Signin.js try using history api.
this.props.history.push('/admin/dashboard')
instead of
<Redirect to='/admin/dashboard' />;

Prevent user from directly accessing URL in React application?

Is there a way to stop the user from directly accessing a URL on my application? For example, we have a page that is accessed as localhost:3000/scheduling but I want to re-route back to the homepage. I couldn't find many helpful articles that could achieve this. I am using React by the way.
Thanks!
You can do it in many ways, this is just an example :
const location = useLocation();
let history = useHistory();
if(location.state == undefined || location.state == null || location.state == ''){
history.push("/");
}
'/' is by default your home page.
You can check this example:
import React from 'react'
import {
BrowserRouter as Router,
Route,
Link,
Redirect,
withRouter
} from 'react-router-dom'
const fakeAuth = {
isAuthenticated: false,
authenticate(cb) {
this.isAuthenticated = true
setTimeout(cb, 100)
},
signout(cb) {
this.isAuthenticated = false
setTimeout(cb, 100)
}
}
const Public = () => <h3>Public</h3>
const Protected = () => <h3>Protected</h3>
class Login extends React.Component {
state = {
redirectToReferrer: false
}
login = () => {
fakeAuth.authenticate(() => {
this.setState(() => ({
redirectToReferrer: true
}))
})
}
render() {
const { from } = this.props.location.state || { from: { pathname: '/' } }
const { redirectToReferrer } = this.state
if (redirectToReferrer === true) {
return <Redirect to={from} />
}
return (
<div>
<p>You must log in to view the page</p>
<button onClick={this.login}>Log in</button>
</div>
)
}
}
const PrivateRoute = ({ component: Component, ...rest }) => (
<Route {...rest} render={(props) => (
fakeAuth.isAuthenticated === true
? <Component {...props} />
: <Redirect to={{
pathname: '/login',
state: { from: props.location }
}} />
)} />
)
export default function AuthExample () {
return (
<Router>
<div>
<ul>
<li><Link to="/public">Public Page</Link></li>
<li><Link to="/protected">Protected Page</Link></li>
</ul>
<Route path="/public" component={Public}/>
<Route path="/login" component={Login}/>
<PrivateRoute path='/protected' component={Protected} />
</div>
</Router>
)
}
Source
We can use Conditional rendering tracing the history.
You can also add conditions using this.props.history.location.key or this.props.history.action
Key exists and action is 'PUSH' when we redirect user using this.props.history.push
Key property doesn't exist and action is 'POP' when a user tries to access the URL directly
return this.props.history.location.key ? (<div></div>) : null

on BackButton: Component does not update although url is changed

When clicking on the back button , the url gets changed, but the component that should be rendered for this url, is not rendered. Rerendering seems to be blocked. I am using connected-react-router. Before implementing connected-react-router, the back button worked fine. I tried a suggested solution , wrapping my connect function with the redux store with withRouter, but still see no result.
index.js
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<ConnectedRouter history={history}>
<App />
</ConnectedRouter>
</Provider>,
document.getElementById('root'),
);
App.js
class App extends Component {
componentDidMount() {
const { dispatch } = this.props;
dispatch(getInitialBookData());
}
render() {
return (
<div>
<Navigation />
</div>
);
}
}
export default connect()(App);
Navigation.js
const Navigation = () => {
return (
<Fragment>
<Navbar color="light" light expand="md">
<Collapse className="navbar-collapse">
<Nav className="ml-sm-auto navbar-nav">
<NavItem className="p-2">
<NavLink activeClassName="active" to="/">Dashboard</NavLink>
</NavItem>
<NavItem className="p-2">
<NavLink activeClassName="active" to="/addbook">Add Book</NavLink>
</NavItem>
</Nav>
</Collapse>
</Navbar>
<Container className="content mt-8">
<Switch>
<Route exact path="/" component={Dashboard} />
<Route exact path="/editbook/:id" component={CreateEditBookContainer} />
<Route exact path="/addbook" component={CreateEditBookContainer} />
<Route component={Page404} />
</Switch>
</Container>
</Fragment>
);
};
Dashboard.js
const Dashboard = ({ loading, error, success }) => {
return (
<Fragment>
{ error.status === true
? <ErrorAlert message={error.message} />
: null }
{ success.status === true
? <SuccessAlert message={success.message} />
: null }
<Row className="mt-3">
<h2 className="mb-5">Welcome to Dashboard</h2>
{ loading === true
? null
: <BookListContainer /> }
</Row>
</Fragment>
);
};
const mapStateToProps = ({fetching, errors, success}) => {
return {
loading: fetching.status,
error: errors,
success: success,
};
}
Dashboard.propTypes = {
loading: PropTypes.bool.isRequired,
error: PropTypes.object.isRequired,
success: PropTypes.object.isRequired,
};
export default withRouter(connect(mapStateToProps)(Dashboard));
BookListContainer.js
class BookListContainer extends Component{
state = {
navigateToEditBook: false,
};
idBookToEdit = null;
goToEditComponent = (id) => {
this.idBookToEdit = id;
this.setState({ navigateToEditBook: true });
}
render() {
let idBookToEdit = this.idBookToEdit;
if (this.state.navigateToEditBook === true) {
return <Redirect push to={{
pathname:`/editBook/${idBookToEdit}`,
state: { referrer: '/', bookId: idBookToEdit }
}} />
}
const { books } = this.props;
return(
<Row>
{ books.map((book) => {
return (
<Book
key={book._id}
book={book}
handleEditBook={this.goToEditComponent}
/>
)
}) }
</Row>
)};
};
const mapStateToProps = (props) => {
return {
books: props.books.books,
location: props.router.location,
};
}
BookListContainer.propTypes = {
books: PropTypes.array.isRequired,
};
export default withRouter((connect(mapStateToProps)(BookListContainer)));
CreateEditBookContainer.js
class CreateEditBookContainer extends Component {
render() {
const bookId = this.props.location.state
? this.props.location.state.bookId
:null
const { books, error, success } = this.props;
let book = null;
if(bookId){
book = books.filter(book => book._id === bookId)
}
return(
<Col sm="12" md="6" className="m-auto pt-5">
<CreateEditBookForm
{ ...book ? book = { ...book[0] } : null }
/>
{ error.status === true
? <ErrorAlert message={error.message} />
: null }
{ success.status === true
? <SuccessAlert message={success.message} />
: null }
</Col>
)}
}
const mapStateToProps = ({ books, errors, success }) => {
return {
books: books.books,
error: errors,
success: success,
}
}
CreateEditBookContainer.propTypes = {
books: PropTypes.array.isRequired,
error: PropTypes.object.isRequired,
success: PropTypes.object.isRequired,
};
export default withRouter(connect(mapStateToProps)(CreateEditBookContainer));
When clicking on EditBook Button, CreateEditBookContainer.js gets correctly rendered, but when clicking on the back button, this components remains, while the Dashboard component should actually get rendered since it corresponds to the path '/', that is correctly set in the url. When clicking on the Route links in specified in Navigation, all components are rendered correctly. It ONLY fails on the back button. Thank you for any suggestions.

Passing props in the Redirect component (React.js) not working as expected

I am working through a practice app to get acquainted with React-Router
In my App component I have a Route which acts as a way to afford for 404's
class App extends Component {
render() {
return (
<Router>
<div>
<Navbar />
<Switch>
<Route path="/" exact component={Home} />
<Route path="/players" component={Players} />
<Route path="/teams" component={Teams} />
{/* params should be last Route as it would match players and teams */}
<Route path="/:teamId" exact component={TeamPage} />
<Route
render={({ location }) => (
<h1 className="text-center">
Sorry, we couldn't find {location.pathname.substr(1)} <br />{" "}
404: page not found.
</h1>
)}
/>
</Switch>
</div>
</Router>
);
}
}
That works fine.
However I have another component TeamPage which essentially has the same mechanism.
import React, { Component } from "react";
import { Redirect, Link } from "react-router-dom";
import { getTeamNames, getTeamsArticles } from "../api";
import TeamLogo from "./TeamLogo";
import Team from "./Team";
import slug from "slug";
export default class TeamPage extends Component {
state = {
loading : true,
teamNames: {},
articles : []
};
componentDidMount() {
Promise.all([
getTeamNames(),
getTeamsArticles(this.props.match.params.teamId)
]).then(([teamNames, articles]) => {
this.setState(() => ({
teamNames,
articles,
loading: false
}));
});
}
render() {
const { loading, teamNames, articles } = this.state;
const { match } = this.props;
const { teamId } = match.params;
if (loading === false && teamNames.includes(teamId) === false) {
return (
<Redirect
to={{
pathname: "/",
location: { location: window.location.pathname.substr(1) }
}}
/>
);
}
return (
<div>
<Team id={teamId}>
{team =>
team === null ? (
<h1>LOADING</h1>
) : (
<div className="panel">
<TeamLogo id={teamId} />
<h1 className="medium-header">{team.name}</h1>
<h4 style={{ margin: 5 }}>
<Link
style = {{ cursor: "pointer" }}
to = {{ pathname: "/players", search: `?teamId=${teamId}` }}
>
View Roster
</Link>
</h4>
<h4>Championships</h4>
<ul className="championships">
{team.championships.map(ship => (
<li key={ship}>{ship}</li>
))}
</ul>
<ul className="info-list row" style={{ width: "100%" }}>
<li>
Established
<div>{team.established}</div>
</li>
<li>
Manager
<div>{team.manager}</div>
</li>
<li>
Coach
<div>{team.coach}</div>
</li>
<li>
Record
<div>
{team.wins}-{team.losses}
</div>
</li>
</ul>
<h2 className="header">Articles</h2>
<ul className="articles">
{articles.map(article => (
<li key={article.id}>
<Link to={`${match.url}/articles/${slug(article.title)}`}>
<h4 className="article-title">{article.title}</h4>
<div className="article-date">
{article.date.toLocaleDateString()}
</div>
</Link>
</li>
))}
</ul>
</div>
)
}
</Team>
</div>
);
}
}
But in this instance I only can accomplish the redirect, with no message presented in the UI.
I am trying to pass props to home or "/" so if someone did that from the TeamPage. The Route in my App component would respond with that message, like it does normally.
Excerpt from TeamPage:
const { loading, teamNames, articles } = this.state;
const { match } = this.props;
const { teamId } = match.params;
if (loading === false && teamNames.includes(teamId) === false) {
return (
<Redirect
to={{
pathname: "/",
location: { location: window.location.pathname.substr(1) }
}}
/>
);
}
Can this be done?
Thanks in advance!
Update Oct 11th 2:56PM
So as per Hannad's great insight I updated my App component and the TeamPage component and created a catch all ErrorPage route. What I need to do now is update the Teams.js file as when I try to go to http://localhost:3000/teams/foo I get the following error:
import React, { Component } from "react";
import { Redirect, Route, Link } from "react-router-dom";
import Sidebar from "./Sidebar";
import { getTeamNames } from "../api";
import TeamLogo from "./TeamLogo";
import Team from "./Team";
export default class Teams extends Component {
state = {
teamNames: [],
loading : true
};
componentDidMount() {
getTeamNames().then(teamNames => {
this.setState(() => ({
loading: false,
teamNames
}));
});
}
render() {
const { loading, teamNames } = this.state;
const { location, match } = this.props;
return (
<div className="container two-column">
<Sidebar
loading = {loading}
title = "Teams"
list = {teamNames}
{...this.props}
/>
{loading === false &&
(location.pathname === "/teams" || location.pathname === "/teams/") ? (
<div className="sidebar-instruction">Select a Team</div>
) : null}
<Route
path = {`${match.url}/:teamId`}
render = {({ match }) => (
<div className="panel">
<Team id={match.params.teamId}>
{team =>
team === null ? (
<h1>Loading</h1>
) : (
<div style={{ width: "100%" }}>
<TeamLogo id={team.id} className="center" />
<h1 className="medium-header">{team.name}</h1>
<ul className="info-list row">
<li>
Established
<div>{team.established}</div>
</li>
<li>
Manager
<div>{team.manager}</div>
</li>
<li>
Coach
<div>{team.coach}</div>
</li>
</ul>
<Link
className = "center btn-main"
to = {`/${match.params.teamId}`}
>
{team.name} Team Page
</Link>
</div>
)
}
</Team>
</div>
)}
/>
</div>
);
}
}
there is an other way to do 404 redirects. which i would suggest. as it has more control..
define your error component which takes simple params.
class App extends Component {
render() {
return (
<Router>
<div>
<Navbar />
<Switch>
<Route path="/" exact component={Home} />
<Route path="/players" component={Players} />
<Route path="/teams" component={Teams} />
{/* params should be last Route as it would match players and teams */}
<Route path="/:teamId" exact component={TeamPage} />
<Route path="/error/:errortype" exact component={ErrorPage} />
</Switch>
</div>
</Router>
);
}
}
// your redirection logic
const { loading, teamNames, articles } = this.state;
const { match } = this.props;
const { teamId } = match.params;
if (loading === false && teamNames.includes(teamId) === false) {
return (
<Redirect
to={'/error/404'}
/>
);
}
// ErrorPage Component
const ErorPage = ({match: { params:{ errortype } }})=>(
// have your logic for different templates for different types of errors.
// ex.
<div>
{
errortype === 404 ?
<div>you got a 404</div> :
<div>sone generic message</div>
// this logic can change with respect to your app.
}
</div>
)

React Router - Preserve current route after page refresh

I am new to SPA development. I use React and React Router and made simple app. It has two pages: public and protected. User can see protected page only when he was signed in. I use firebase for managing users.
The problem is when I go to protected page, log in and can see its content, I then resresh the page and redirected to "default" state which is public page.
index.js
ReactDOM.render(<App />, document.getElementById('root'));
App.js
const ProtectedPage = () => {
return (
<div>
<h1> Protected page. </h1>
<Link to="/">Public resources</Link>
</div>
);
}
const PublicPage = () => {
return (
<div>
<h1> Public page. </h1>
<p> Login to see protected resources </p>
<Link to="/protected">Protected resources</Link>
</div>
);
}
const PrivateRoute = ({ component: Component, isAuthenticated, ...rest }) => (
<Route {...rest} render={props => (
isAuthenticated ? (
<Component {...props}/>
) : (
<Redirect to={{
pathname: '/',
state: { from: props.location }
}}/>
)
)}/>
)
class App extends React.Component {
constructor(props) {
super(props);
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
this.state = {
username: '',
uuid: ''
}
}
login() {
auth.signInWithEmailAndPassword(email, password).then((user) => {
console.log('Sign in ', user.displayName);
this.setState({
username: user.displayName,
uuid: user.uuid
});
});
}
logout() {
auth.signOut().then(() => {
console.log('Sign out ');
this.setState({
username: '',
uuid: ''
});
})
}
componentDidMount() {
this.releaseFirebaseAuthHandler = auth.onAuthStateChanged((user) => {
if(user) {
this.setState({
username: user.displayName,
uuid: user.uuid
});
}
});
}
componentWillUnmount() {
this.releaseFirebaseAuthHandler();
}
render() {
return (
<div>
<div className="navbar">
<nav className="navbar-nav">
{ this.state.username ?
<button onClick={this.logout}>Logout</button> :
<button onClick={this.login}>Login</button> }
</nav>
</div>
<div>
<div>
<Route exact path="/" component={PublicPage} />
<PrivateRoute path="/protected" component={ProtectedPage} isAuthenticated={this.state.uuid !== ''} />
</div>
</div>
</div>
);
}
}
export default App;
Is it possible to preserve localhost:3000/protected page after refreshing it without involving the server?
In your code you redirect to pathname: '/' if user is not authenticated:
const PrivateRoute = (...) => (
<Route {...rest} render={props => (
isAuthenticated
? <Component {...props}/>
: <Redirect to={{
pathname: '/',
state: { from: props.location }
}}/>
)}/>
)
If you would like to keep /protected in url one way to do it is to replace redirect with something like <div>Please login</div>
The state is not preserved across multiple sessions, for this purpose you can use the localStorage.

Resources