A modern React + TypeScript frontend for a full-stack recipe management application. This UI lets users search for recipes, view details, and manage favourites, with a beautiful, responsive design and seamless backend integration.
- Live-Demo: https://recipe-smart.vercel.app/
- Project Overview
- Project Structure
- Features & Functionality
- Environment Variables (.env)
- Setup & Usage
- Code Walkthrough
- Reusing Components & Extending
- Tech Stack & Keywords
- Conclusion
- Happy Coding! 🎉
This frontend is the user interface for the Recipe App. It allows users to:
- Search for recipes using keywords
- View recipe summaries in a modal
- Add or remove favourite recipes
- Switch between search and favourites tabs
- Enjoy a responsive, modern UI
recipe-app-frontend/
├── public/
│ ├── hero-image.webp
│ └── vite.svg
├── src/
│ ├── App.tsx # Main app logic and UI
│ ├── App.css # Styles
│ ├── api.ts # API integration
│ ├── main.tsx # Entry point
│ ├── types.ts # TypeScript types
│ └── components/
│ ├── RecipeCard.tsx # Recipe card component
│ └── RecipeModal.tsx # Modal for recipe summary
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts- Recipe Search: Search recipes by keyword, paginated results
- Recipe Details: View recipe summary in a modal with clickable links
- Favourites: Add/remove recipes to your favourites, view in a separate tab
- Responsive UI: Works on desktop and mobile
- Modern UX: Animated modal, hover effects, empty state messages
- TypeScript: Type safety throughout
- API Integration: Connects to backend REST API
Create a .env file in the frontend root with:
# Backend API base URL (e.g., http://localhost:5005 or your deployed backend)
NEXT_PUBLIC_API_URL=- NEXT_PUBLIC_API_URL: The base URL for your backend API. Leave empty to use relative paths (works with Next.js automatically). For production, use your deployed backend URL if needed.
-
Install dependencies:
npm install
-
Configure environment:
- Create a
.envfile as shown above.
- Create a
-
Start the frontend app:
npm run dev
-
Open in browser:
- Visit http://localhost:5173 (default Vite port)
- Main app logic: manages state, tabs, search, favourites, and modal.
- Uses hooks (
useState,useEffect,useRef) for state and lifecycle. - Handles API calls via
api.ts.
const handleSearchSubmit = async (event: FormEvent) => {
event.preventDefault();
setApiError("");
try {
const response = await api.searchRecipes(searchTerm, 1);
if (response.status === "failure" || response.code === 402) {
setApiError(response.message || "API quota reached.");
setRecipes([]);
} else {
setRecipes(Array.isArray(response.results) ? response.results : []);
pageNumber.current = 1;
}
} catch (e) {
setRecipes([]);
}
};- Handles all API requests to the backend.
- Uses
fetchand theNEXT_PUBLIC_API_URLenvironment variable (or relative paths if empty).
export const addFavouriteRecipe = async (recipe: Recipe) => {
const url = new URL("/api/recipes/favourite", API_URL);
const body = { recipeId: recipe.id };
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
};- Displays a recipe card with image, title, and favourite button.
- Handles click events for viewing details and toggling favourites.
<RecipeCard
key={recipe.id}
recipe={recipe}
onClick={() => setSelectedRecipe(recipe)}
onFavouriteButtonClick={
isFavourite ? removeFavouriteRecipe : addFavouriteRecipe
}
isFavourite={isFavourite}
/>- Shows a modal with the recipe summary and title.
- Fetches summary from backend on open.
- Ensures links open in a new tab.
{
selectedRecipe ? (
<RecipeModal
recipeId={selectedRecipe.id.toString()}
onClose={() => setSelectedRecipe(undefined)}
/>
) : null;
}- TypeScript interfaces for recipe data.
Example:
export interface Recipe {
id: number;
title: string;
image: string;
imageType: string;
}- Styles for layout, modal, cards, buttons, and responsive design.
- Uses CSS grid for recipe layout and media queries for responsiveness.
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2;
max-height: 90vh;
width: 90vw;
max-width: 600px;
overflow-y: auto;
border-radius: 16px;
background: #fff;
}- RecipeCard: Use in any recipe grid/list. Pass
recipe,onClick,onFavouriteButtonClick, andisFavouriteprops. - RecipeModal: Use for any modal summary/details. Pass
recipeIdandonClose. - api.ts: Import and use API functions in any React component.
- Types: Import from
types.tsfor type safety in new components. - Easy Extension: Add new tabs, features, or endpoints by following the modular structure.
export const getRecipeInstructions = async (recipeId: string) => {
const url = new URL(`/api/recipes/${recipeId}/instructions`, API_URL);
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch instructions");
return response.json();
};- React
- TypeScript
- Vite
- REST API
- Modular Components
- Hooks
- Responsive Design
- CSS Grid
- Frontend for Backend
- API Integration
- Reusable UI
This frontend is a modern, extensible, and user-friendly interface for recipe discovery and management. It demonstrates best practices in React, TypeScript, API integration, and UI/UX design. Use it as a learning resource, a starter for your own projects, or as a frontend for your backend APIs.
Feel free to use this project repository and extend this project further!
If you have any questions or want to share your work, reach out via GitHub or my portfolio at https://arnob-mahmud.vercel.app/.
Enjoy building and learning! 🚀
Thank you! 😊