Episode 9: Persistent Login, Auto Logout & UI Polish with TailwindCSS
Auto login persists between page loads, and set up auto logout after a period of inactivity.
Episode 9: Persistent Login, Auto Logout & UI Polish with TailwindCSS
In this episode, you will:
- Implement persistent login using
localStorage - Automatically log users out after 10 minutes
- Polish the UI using TailwindCSS
- Make layout responsive across screen sizes
Part 1: Persistent Login & Auto Logout
1. Persistent Login (Already Done with LocalStorage)
Since we already save user info in localStorage, the session persists between refreshes.
Now, we’ll add a loginTime and manage session expiration.
2. Save Login Time
Update the login logic in Login.jsx:
1
2
localStorage.setItem("user", JSON.stringify(user));
localStorage.setItem("loginTime", Date.now());
3. Enhance PrivateRoute.jsx to Check Timeout
Update src/components/PrivateRoute.jsx:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import { Navigate } from "react-router-dom";
const SESSION_TIMEOUT = 10 * 60 * 1000; // 10 minutes
const PrivateRoute = ({ children }) => {
const user = JSON.parse(localStorage.getItem("user"));
const loginTime = parseInt(localStorage.getItem("loginTime"));
const now = Date.now();
const expired = now - loginTime > SESSION_TIMEOUT;
if (!user || expired) {
localStorage.removeItem("user");
localStorage.removeItem("loginTime");
return <Navigate to="/login" />;
}
return children;
};
export default PrivateRoute;
4. Optional: Show Logout Countdown
Inside Navbar.jsx, show how long before session expires:
1
2
const loginTime = parseInt(localStorage.getItem("loginTime"));
const remaining = Math.max(0, 10 * 60 - Math.floor((Date.now() - loginTime) / 1000));
You can render this with:
1
<span className="text-sm text-gray-200">Session: {secondsLeft}s</span>
- Test the Flow
- Log in
- Refresh page => still logged in => Sucess
- Wait 10+ minutes => try to access /cart => Can’t accesss and redirected to login page
Part 2: Polish UI with TailwindCSS
1. Product Card Styling
In ProductCard.jsx:
1
2
3
4
5
6
7
8
<div className="bg-white rounded-2xl shadow-md p-4 flex flex-col hover:shadow-lg transition">
<img src={product.image} className="w-full h-48 object-cover rounded-lg mb-4" />
<h2 className="text-lg font-semibold line-clamp-2">{product.title}</h2>
<p className="text-blue-600 font-bold mt-2">${product.price}</p>
<button className="mt-auto bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700 transition">
Add to Cart
</button>
</div>
2. Responsive Grid in Home.jsx
1
2
3
4
5
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
3. Cart Item Styling in Cart.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div className="border p-4 rounded-xl flex flex-col sm:flex-row justify-between gap-4 items-center">
<div>
<h2 className="font-semibold text-lg">{item.title}</h2>
<p className="text-gray-600">${item.price}</p>
<div className="flex items-center gap-2 mt-2">
<button className="bg-gray-200 px-2 py-1 rounded" onClick={...}>−</button>
<span>{item.quantity}</span>
<button className="bg-gray-200 px-2 py-1 rounded" onClick={...}>+</button>
</div>
</div>
<div className="text-right">
<p className="font-bold">${(item.price * item.quantity).toFixed(2)}</p>
<button className="text-red-500 mt-2 hover:underline" onClick={...}>Remove</button>
</div>
</div>
4. Responsive Navbar
In Navbar.jsx:
1
2
3
4
5
6
7
8
<nav className="flex items-center justify-between p-4 bg-blue-600 text-white flex-wrap">
<div className="text-xl font-bold">E-Shop</div>
<div className="space-x-4 mt-2 sm:mt-0">
<Link to="/" className="hover:underline">Home</Link>
<Link to="/cart" className="hover:underline">Cart</Link>
<Link to="/login" className="hover:underline">Login</Link>
</div>
</nav>
This post is licensed under CC BY 4.0 by the author.