Our project’s purpose is to provide a platform through which the food ordering and delivery process becomes easy for both the customers and food service providers. It also has an administrator role to manage the customers and food service providers of the application. Our goal is to enhance the overall food experience by providing a platform for effective order tracking and meal management. With features like tracking deliveries in real-time, earning reward points, and easy-to-use dashboards, we aim to make the experience smooth for everyone.
- Date Created: 30 May 2024
- Last Modification Date: August 09, 2024
- Deployment URL: https://tiffinbox-csci5709.netlify.app/
- Git URL: https://git.cs.dal.ca/rkp/csci-5709-grp-04
- Raj Kamlesh Patel - (Full Stack Developer)
- Keval Dharmeshbhai Gandevia - (Full Stack Developer)
- Harsh Maisuri - (Full Stack Developer)
- Kunj Hiteshkumar Pathak - (Full Stack Developer)
- Savan Maheshkumar Patel - (Full Stack Developer)
- Bhavya Mukesh Dave - (Full Stack Developer)
To have a local copy of this project up and running on your local machine, you will first need to install the following:
Clone the Repository
Clone with HTTPS
git clone https://git.cs.dal.ca/rkp/csci-5709-grp-04.git
OR
Clone with SSH
git clone [email protected]:rkp/csci-5709-grp-04.git
cd csci-5709-grp-04/frontend
npm install
npm run dev
Frontend should be running on http://localhost:5173/
cd csci-5709-grp-08/backend/
mvn spring-boot:run
The backend should be running on http://localhost:8080/
This app has been deployed on Netlify (Frontend) and Render (Backend).
- Frontend Deployed App URL: https://tiffinbox-csci5709.netlify.app/
- Backend Deployed App URL: https://tiffin-box.onrender.com
Deployment to Netlify
- Click "Add new site".
- Connect your GitHub account and select your repository.
- Base directory: frontend
- Build Command: npm run build
- Publish Directory: /frontend/dist
- Deploy: Click "Deploy site".
- React - The JavaScript library used for building the user interface.
- Vite - The build tool used for faster and leaner development.
- Tailwind CSS - Utility-first CSS framework for rapidly building modern websites.
- Daisy UI - Tailwind CSS component library.
- npm - Dependency Management.
- Spring Boot - The backend framework used
- Java - The programming language used
- Maven - Used as a build tool and for dependency management.
- Docker - Used for containerization.
- MongoDB - Database used.
Lines 25 - 119
<nav className="max-w-5xl navbar">
<div className="gap-2 navbar-start">
<div className="dropdown">
<div tabIndex={0} role="button" className="btn btn-ghost lg:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
className="w-5 h-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h8m-8 6h16"
/>
</svg>
</div>
<ul
tabIndex={0}
className="menu menu-sm dropdown-content mt-3 gap-2 z-[1] p-2 shadow bg-base-100 rounded-box w-52"
>
<li>
<NavLink
to="/"
className={({ isActive }) => (isActive ? "active" : "")}
>
Home
</NavLink>
</li>
<li>
<NavLink
to="/contact-us"
className={({ isActive }) => (isActive ? "active" : "")}
>
Contact Us
</NavLink>
</li>
<li>
<NavLink
to="/faqs"
className={({ isActive }) => (isActive ? "active" : "")}
>
FAQs
</NavLink>
</li>
</ul>
</div>
<Link to="/" className="cursor-pointer">
<img
src="https://res.cloudinary.com/dk1fim9hl/image/upload/v1719262725/Tiffin%20Box/nldinb3ipt9tegyc2hzs.png"
alt="tiffin box"
className="w-10"
/>
</Link>
</div>
<div className="hidden navbar-center lg:flex">
<ul className="flex gap-8 px-1">
<li>
<NavLink
to="/"
className={({ isActive }) =>
isActive ? "text-secondary" : "hover:text-primary transition"
}
>
Home
</NavLink>
</li>
<li>
<NavLink
to="/contact-us"
className={({ isActive }) =>
isActive ? "text-secondary" : "hover:text-primary transition"
}
>
Contact Us
</NavLink>
</li>
<li>
<NavLink
to="/faqs"
className={({ isActive }) =>
isActive ? "text-secondary" : "hover:text-primary transition"
}
>
FAQs
</NavLink>
</li>
</ul>
</div>
<div className="navbar-end">
<Link className="text-slate-100 btn btn-secondary">Login</Link>
</div>
</nav>
The code above was created by adapting the code in Navbar - Daisy UI as shown below:
<div className="navbar bg-base-100">
<div className="navbar-start">
<div className="dropdown">
<div tabIndex={0} role="button" className="btn btn-ghost lg:hidden">
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M4 6h16M4 12h8m-8 6h16" />
</svg>
</div>
<ul
tabIndex={0}
className="menu menu-sm dropdown-content bg-base-100 rounded-box z-[1] mt-3 w-52 p-2 shadow">
<li><a>Item 1</a></li>
<li>
<a>Parent</a>
<ul className="p-2">
<li><a>Submenu 1</a></li>
<li><a>Submenu 2</a></li>
</ul>
</li>
<li><a>Item 3</a></li>
</ul>
</div>
<a className="btn btn-ghost text-xl">daisyUI</a>
</div>
<div className="navbar-center hidden lg:flex">
<ul className="menu menu-horizontal px-1">
<li><a>Item 1</a></li>
<li>
<details>
<summary>Parent</summary>
<ul className="p-2">
<li><a>Submenu 1</a></li>
<li><a>Submenu 2</a></li>
</ul>
</details>
</li>
<li><a>Item 3</a></li>
</ul>
</div>
<div className="navbar-end">
<a className="btn">Button</a>
</div>
</div>
Lines 5-29
<footer className="p-10 rounded footer footer-center bg-secondary text-accent-content">
<nav className="grid grid-flow-col gap-4 font-medium">
<Link to="/" className="link link-hover">
Home
</Link>
<Link to="/contact-us" className="link link-hover">
Contact
</Link>
<Link to="/faqs" className="link link-hover">
FAQ
</Link>
</nav>
<a>
<img
src="https://res.cloudinary.com/dk1fim9hl/image/upload/v1719262725/Tiffin%20Box/nldinb3ipt9tegyc2hzs.png"
alt="tiffin box"
className="w-10"
/>
</a>
<aside>
<p className="font-medium">
Copyright © 2024 - All right reserved by Tiffin Box
</p>
</aside>
</footer>
The code above was created by adapting the code in Footer - Daisy UI as shown below:
<footer className="footer footer-center bg-base-200 text-base-content rounded p-10">
<nav className="grid grid-flow-col gap-4">
<a className="link link-hover">About us</a>
<a className="link link-hover">Contact</a>
<a className="link link-hover">Jobs</a>
<a className="link link-hover">Press kit</a>
</nav>
<nav>
<div className="grid grid-flow-col gap-4">
<a>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className="fill-current">
<path
d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"></path>
</svg>
</a>
<a>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className="fill-current">
<path
d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"></path>
</svg>
</a>
<a>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
className="fill-current">
<path
d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"></path>
</svg>
</a>
</div>
</nav>
<aside>
<p>Copyright © ${new Date().getFullYear()} - All right reserved by ACME Industries Ltd</p>
</aside>
</footer>
Lines 7 - 22
<section className="max-w-5xl overflow-hidden rounded-md shadow-md hero h-96 bg-bgHero">
<div className="hero-overlay bg-opacity-70"></div>
<div className="text-center hero-content text-neutral-content">
<div className="max-w-3xl">
<h1 className="mb-5 text-4xl font-bold md:text-6xl">
Delicious Home-Cooked Meals
</h1>
<h2 className="mb-5 text-2xl font-semibold sm:mb-7 sm:text-2xl">
Find the best tiffins near you
</h2>
<button className="px-8 text-xl text-white btn btn-secondary">
Explore <BiSolidDish className="w-6 h-6 ml-2" />
</button>
</div>
</div>
</section>
The code above was created by adapting the code in Hero - Daisy UI as shown below:
<div
className="hero min-h-screen"
style={{
backgroundImage: "url(https://daisyui.com/images/stock/photo-1507358522600-9f71e620c44e.jpg)",
}}>
<div className="hero-overlay bg-opacity-60"></div>
<div className="hero-content text-neutral-content text-center">
<div className="max-w-md">
<h1 className="mb-5 text-5xl font-bold">Hello there</h1>
<p className="mb-5">
Provident cupiditate voluptatem et in. Quaerat fugiat ut assumenda excepturi exercitationem
quasi. In deleniti eaque aut repudiandae et a id nisi.
</p>
<button className="btn btn-primary">Get Started</button>
</div>
</div>
</div>
- Lines 34 - 49
http.csrf(AbstractHttpConfigurer::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(request -> request
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasAnyAuthority(UserRole.ADMIN.name())
.requestMatchers("/api/home/**").permitAll()
.requestMatchers("/api/meal/**").permitAll()
.requestMatchers("/api/orders/**").authenticated()
.requestMatchers("/api/foodserviceprovider/**").authenticated()
.requestMatchers("/api/ordertrack/**").authenticated()
.requestMatchers("/api/subscription/**").authenticated()
.anyRequest().authenticated()
).sessionManagement(manager -> manager.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
The code above was created by adapting the code in Ali Bouli Github as shown below:
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(req ->
req.requestMatchers(WHITE_LIST_URL)
.permitAll()
.requestMatchers("/api/v1/management/**").hasAnyRole(ADMIN.name(), MANAGER.name())
.requestMatchers(GET, "/api/v1/management/**").hasAnyAuthority(ADMIN_READ.name(), MANAGER_READ.name())
.requestMatchers(POST, "/api/v1/management/**").hasAnyAuthority(ADMIN_CREATE.name(), MANAGER_CREATE.name())
.requestMatchers(PUT, "/api/v1/management/**").hasAnyAuthority(ADMIN_UPDATE.name(), MANAGER_UPDATE.name())
.requestMatchers(DELETE, "/api/v1/management/**").hasAnyAuthority(ADMIN_DELETE.name(), MANAGER_DELETE.name())
.anyRequest()
.authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.logout(logout ->
logout.logoutUrl("/api/v1/auth/logout")
.addLogoutHandler(logoutHandler)
.logoutSuccessHandler((request, response, authentication) -> SecurityContextHolder.clearContext())
)
;
return http.build();
- The code in Ali Bouli Github was taken as a reference from github repository.
- Ali Bouli Github This code was used to implement security configuration file.
- Ali Bouli Github This code was modified by removing the logout feature and modified accoording to our feature.
- Lines 16 - 39
<div className="card bg-base-100 shadow-md">
<div className="card-body">
<h1 className="card-title text-3xl">
<p className="text-center">Total Users</p>
</h1>
<p className="font-semibold text-center">{analysisDetails?.totalUsers || 0}</p>
</div>
</div>
<div className="card w-50 bg-base-100 shadow-md">
<div className="card-body">
<h1 className="card-title text-3xl">
<p className="text-center">Total Orders</p>
</h1>
<p className="font-semibold text-center">{analysisDetails?.totalOrders || 0}</p>
</div>
</div>
<div className="card w-50 bg-base-100 shadow-md">
<div className="card-body">
<h1 className="card-title text-3xl">
<p className="text-center">Total Earnings</p>
</h1>
<p className="font-semibold text-center">${analysisDetails?.totalEarnings || 0}</p>
</div>
</div>
The code above was created by adapting the code in daisyUI as shown below:
<div className="card bg-base-100 w-96 shadow-xl">
<div className="card-body">
<h2 className="card-title">Card title!</h2>
<p>If a dog chews shoes whose shoes does he choose?</p>
<div className="card-actions justify-end">
<button className="btn btn-primary">Buy Now</button>
</div>
</div>
</div>
- The code in daisyUI was taken as a reference from the official documentation.
- daisyUI This code was used to implement responsive card components with grids defined by me.
- daisyUI This code was modified by changing internal elements such as I have removed the actions, changing the title, and so on.
- Lines 64 - 101
<div className="overflow-x-auto">
<table className="table">
<thead>
<tr>
<th>Name</th>
<th>Company</th>
<th>Email</th>
<th>Contact Number</th>
<th>View</th>
</tr>
</thead>
<tbody>
{filteredRows.map((item) => (
<tr key={item.userId}>
<td>{item.name}</td>
<td>{item.companyName}</td>
<td>{item.email}</td>
<td>{item.contact}</td>
<td>
<button
className="btn btn-primary"
onClick={() => handleViewClick(item)}
>
View
</button>
</td>
</tr>
))}
</tbody>
</table>
{filteredRows.length === 0 ? (
<h1 className="text-xl text-center font-bold my-2">
No Pending Requests!
</h1>
) : (
<></>
)}
</div>
The code above was created by adapting the code in daisyUI as shown below:
<div className="overflow-x-auto">
<table className="table">
{/* head */}
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Job</th>
<th>Favorite Color</th>
</tr>
</thead>
<tbody>
{/* row 1 */}
<tr>
<th>1</th>
<td>Cy Ganderton</td>
<td>Quality Control Specialist</td>
<td>Blue</td>
</tr>
{/* row 2 */}
<tr>
<th>2</th>
<td>Hart Hagerty</td>
<td>Desktop Support Technician</td>
<td>Purple</td>
</tr>
{/* row 3 */}
<tr>
<th>3</th>
<td>Brice Swyre</td>
<td>Tax Accountant</td>
<td>Red</td>
</tr>
</tbody>
</table>
</div>
- The code in daisyUI was taken as a reference from the official documentation.
- daisyUI This code was used to implement table as per needed attributes.
- daisyUI This code was modified by changing the dummy rows. I have used the map function of JavaScript to display rows. And conditional logic is added to display rows.
- Lines 64 - 101
<table className="table">
<thead>
<tr>
<th>Order ID</th>
<th>Customer Name</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{filteredRows.length !== 0 &&
filteredRows.map((order) => (
<tr key={order.orderId}>
<td>{order.orderId}</td>
<td>{order.customerName}</td>
<td>
<Link
to={`/foodprovider/received-orders/${order.orderId}`}
className="btn btn-neutral"
>
View
</Link>
</td>
</tr>
))}
</tbody>
</table>
The code above was created by adapting the code in daisyUI as shown below:
<table className="table">
{/* head */}
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Job</th>
<th>Favorite Color</th>
</tr>
</thead>
<tbody>
{/* row 1 */}
<tr>
<th>1</th>
<td>Cy Ganderton</td>
<td>Quality Control Specialist</td>
<td>Blue</td>
</tr>
{/* row 2 */}
<tr>
<th>2</th>
<td>Hart Hagerty</td>
<td>Desktop Support Technician</td>
<td>Purple</td>
</tr>
{/* row 3 */}
<tr>
<th>3</th>
<td>Brice Swyre</td>
<td>Tax Accountant</td>
<td>Red</td>
</tr>
</tbody>
</table>
- The code in daisyUI was taken as a reference from the official documentation.
- daisyUI This code was used to implement table as per needed attributes.
- daisyUI This code was modified by changing the dummy rows. I have used the map function of JavaScript to display rows. And conditional logic is added to display rows.
Line 35 - 40
@PostMapping("/addReview")
public ResponseEntity<BasicResponse> addReview(@Valid @RequestBody ReviewRequest reviewRequest, Principal principal) {
BasicResponse savedReview = reviewService.addReview(reviewRequest,principal);
return ResponseEntity.status(HttpStatus.OK).body(savedReview);
}
The code above was created by adapting the code from How does Spring Security inject principal into Controller? as shown below:
@Nullable
private Object resolveArgument(Class<?> paramType, HttpServletRequest request) throws IOException {
//omitted......
else if (Principal.class.isAssignableFrom(paramType)) {
Principal userPrincipal = request.getUserPrincipal();
if (userPrincipal != null && !paramType.isInstance(userPrincipal)) {
throw new IllegalStateException(
"Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal);
}
return userPrincipal;
}
//omitted......
}
-
The code in How does Spring Security inject principal into Controller? provided by Holinc was implemented by propely understanding the working of java functions such as streams(), map() and filter(). After Understanding, I have modified the code as per my requirement.
-
The code example discussed in How does Spring Security inject principal into Controller? provided by Holinc was instrumental in illustrating how Spring Security injects the Principal into a controller. By thoroughly understanding how Java functions such as streams(), map(), and filter() work, I was able to adapt the example to fit my specific needs.
-
How does Spring Security inject principal into Controller?'s Code provided by Holinc was used because it gave me the idea on how to filter out FoodProviders based on provided search filter.
-
How does Spring Security inject principal into Controller?'s Code provided by Holinc was modified by using the Principal as the parameter in the Controller.
Line 69-78
List<Review> reviews = reviewRepository.findAllByFoodServiceProvider(foodServiceProvider.get());
return reviews.stream().map(review -> {
ReviewResponse response = new ReviewResponse();
response.setReviewDescription(review.getReviewDescription());
response.setReviewStars(review.getReviewStars());
response.setFirstName(review.getCustomer().getFirstName());
response.setLastName(review.getCustomer().getLastName());
return response;
}).collect(Collectors.toList());
The code above was created by adapting the code in Mapping stream in DTO or passing mapped value to DTO separately? as shown below:
public RecipeResponse findById(Long id) {
return recipeRepository.findById(id).map(recipe -> {
RecipeResponse recipeResponse = new RecipeResponse();
recipeResponse.setId(id);
recipeResponse.setTitle(recipe.getTitle());
recipeResponse.setIngredients(
recipe.getRecipeIngredients().stream().map((recipeIngredient) -> {
RecipeIngredientResponse recipeIngredientResponse = new RecipeIngredientResponse();
// set the RecipeIngredientResponse properties here
return recipeIngredientResponse;
}).collect(Collectors.toList()));
return new RecipeResponse();
}).orElseThrow(() -> new NoSuchElementFoundException("Not found"));
}
-
Mapping stream in DTO or passing mapped value to DTO separately? was implemented by properly reading the original source and understanding how mapping is used to return the response dto.
-
Mapping stream in DTO or passing mapped value to DTO separately?'s code was used because it helped me return the response using the .stream().map().
-
Mapping stream in DTO or passing mapped value to DTO separately?'s Code was modified by adding other variables for the response object.
Line 54 - 58
try {
const abc= await api.post('http://localhost:8080/api/reviews/addReview', reviewData);
console.log(abc)
toast.success('Your review has been successfully submitted.', {
position: "top-center",
duration: 2000
});
}
The code above was created by adapting the code in How to Make POST Requests with Axios as shown below:
axios.post('https://api.example.com/post-endpoint', {key1: 'value1',key2: 'value2',
}, {headers: {'Content-Type': 'application/json','Authorization': 'Bearer YOUR_ACCESS_TOKEN',
},
})
.then(response => {console.log('Response:', response.data);
})
.catch(error => {console.error('Error:', error);
});
-
How to Make POST Requests with Axios was implemented by properly reading the original source and understanding how to make the post requests using the axios.
-
How to Make POST Requests with Axios's Code was used because because it helped to make the axios post requiest which was used to fetch the data from the back end.
-
How to Make POST Requests with Axios's Code was modified by adding the authorization bearer token.
Lines 35 - 45
const handleImageChange = (e) => {
const file = e.target.files?.[0];
if (file && file.type.startsWith("image/")) {
const reader = new FileReader();
reader.onloadend = () => {
setAvatar(reader.result);
};
reader.readAsDataURL(file);
}
updateProfileImage(e.target.files[0]);
};
- The code above was created by adapting code generated by chat gpt Chatgpt
- ChatGPT Prompt: Write a React function to handle and preview an uploaded image.
Lines 107 - 132
<div className="flex flex-col space-x-0 md:flex-row space-y-4 md:space-y-0 md:space-x-6 mt-4">
<div className="w-full flex flex-col">
<label htmlFor="email">Email</label>
<input
type="email"
name="email"
placeholder="Email"
readOnly
className="input input-bordered w-full mt-4"
value={"email"}
onChange={handleChange}
/>
</div>
<div className="w-full flex flex-col">
<label htmlFor="contact">Contact</label>
<input
type="tel"
name="contact"
placeholder="Contact"
className="input input-bordered w-full mt-4"
value={"123456789"}
onChange={handleChange}
/>
</div>
</div>
- The code above was created by adapting code generated by chat gpt Chatgpt
- ChatGPT Prompt: Create a responsive React form with email and contact fields, supporting both mobile and desktop layouts using Tailwind CSS.
Line 46 - 48
foodProviderResponseDTOList = sellerRepository.findByCity(city).stream()
.map(this::convertToFoodProviderDTO)
.filter(foodProviderResponseDTO ->
foodProviderResponseDTO.getCuisineType()
.contains(searchFoodProviderRequest.getCuisineType()))
.toList();
The code above was created by adapting the code from Stack Overflow's chat threaad as shown below:
List<YearBrandDTO> years = yearRepository.findAll().stream()
.map(year -> {
List<Brand> filteredBrands = year.getBrands().stream()
.filter(brand -> brand.getName().equals("Toyota"))
.collect(Collectors.toList());
-
The code in Stack Overflow's chat thread provided by devblack-exe User was implemented by propely understanding the working of java functions such as streams(), map() and filter(). After Understanding, I have modified the code as per my requirement.
-
Stack Overflow's chat thread's Code provided by devblack-exe User was used because it gave me the idea on how to filter out FoodProviders based on provided search filter.
-
Stack Overflow's chat thread's Code provided by devblack-exe User was modified by using the combination of map() and filter() function together to meet the requirement.
Line 41 - 47
@RequestPart("mealImage") MultipartFile mealImage,
@RequestPart("mealName") String mealName,
@RequestPart("mealDescription") String mealDescription,
@RequestPart("mealPrice") String mealPrice,
@RequestPart("mealType") String mealType,
@RequestPart("cuisineType") String cuisineType
The code above was created by adapting the code in Multipart File with Springboot as shown below:
public ResponseEntity<String> uploadFile(@RequestPart(value = "file") MultipartFile file) {
service.uploadFile(file);
return new ResponseEntity<>("success", HttpStatus.OK);
}
-
Multipart File with Springboot was implemented by properly reading the original source and understanding how Multipart files are recieved in the backend.
-
Multipart File with Springboot's Code was used because it provided the option to handle image upload without needing to convert the image to base64. This option also decreases frontEnd work as files will be passed to backend as they are uploaded by User.
-
Multipart File with Springboot's Code was modified by also accepting other details about meals such as mealName, mealDescription etc as part of the formData.
Line 24 - 35
const handleImageChange = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => {
setPreview(reader.result);
};
reader.readAsDataURL(file);
}
setMealData({ ...mealData, mealImage: file });
console.log(e.target.files[0]);
};
Line 78-87
{preview && (
<div className="mt-4">
<p className="text-md font-bold text-gray-600">Image Preview:</p>
<img
src={preview}
alt="Preview"
className="max-w-full h-auto border border-gray-300 rounded-lg"
/>
</div>
)}
The code above was created by adapting the code in How to display preview of an image in React as shown below:
useEffect(() => {
if (!file) {
return
}
const reader = new FileReader()
reader.onloadend = () => {
setPreviewUrl(reader.result)
}
reader.readAsDataURL(file)
}, [file])
return (
<>
<input type="file" onChange={(e) => setFile(e.target.files[0])} />
{previewUrl && <img src={previewUrl} alt="Preview" />}
</>
)
}
// Add a response interceptor
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// If the error status is 401 and there is no originalRequest._retry flag,
// it means the token has expired and we need to refresh it
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
const refreshToken = localStorage.getItem('refreshToken');
const response = await axios.post('/api/refresh-token', { refreshToken });
const { token } = response.data;
localStorage.setItem('token', token);
// Retry the original request with the new token
originalRequest.headers.Authorization = `Bearer ${token}`;
return axios(originalRequest);
} catch (error) {
// Handle refresh token error or redirect to login
}
}
return Promise.reject(error);
}
);
The code above was developed using the code below as reference to setup the frontend for the refresh token medium
import axios from 'axios';
const api = axios.create({
baseURL: '/api',
});
// Add a request interceptor
api.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
export default api
- The code in daisyUI was taken as a reference from the official documentation.
- daisyUI This code was used to implement table as per needed attributes.
- daisyUI This code was modified by changing the dummy rows. I have used the map function of JavaScript to display rows. And conditional logic is added to display rows.
- I want to extend my gratitude to the creators and developers of the sources mentioned above. Their ideas and it's implementation really helped me to complete as well as provide some additional functionality to my feature. It helped in creating the better User Experience.
- This Article named How to implement search... on GeeksForGeeks gave me idea of how to implement Search Functionality in ReactJs FrontEnd.
- Spring Boot
- Render
- Docker
- ViteJS
- daisyUI
- Tailwind CSS
- MongoDB Atlas
- Netlify
- Cloudinary