Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Layout/AppLayout.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function AppLayout() {
<div className="min-h-screen bg-center bg-cover">
<Navbar />
<SideCartDrawer />
<Toaster
<Toaster
position="top-right"
richColors
expand={false}
Expand Down
7 changes: 1 addition & 6 deletions src/components/Dashboard/ChefMenuView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
} from "../../hooks/dashboard/useMenuItems";
import { useTrendingMenus } from "../../hooks/dashboard/useDashboard";
import { FiEdit2, FiPlus, FiTrash2 } from "react-icons/fi";
import { useToast } from "../../store/toastStore";
import { DashboardPageSkeleton } from "./shared/DashboardSkeleton";
import ErrorState from "./shared/ErrorState";
import EmptyState from "./shared/EmptyState";
Expand Down Expand Up @@ -43,16 +42,12 @@ function ChefMenuView() {
const [isInactiveModalOpen, setIsInactiveModalOpen] = useState(false);

const navigate = useNavigate();
const { addToast } = useToast();
const { mutate: deleteItem } = useDeleteMenuItem();

// ── CRUD handlers ──────────────────────────────────────────────────────────
const confirmDelete = () => {
if (!deletingId) return;
deleteItem(deletingId, {
onSuccess: () => addToast("Menu item deleted", "success"),
onError: () => addToast("Failed to delete menu item", "error"),
});
deleteItem(deletingId);
setDeletingId(null);
};

Expand Down
4 changes: 2 additions & 2 deletions src/components/Dashboard/DashboardHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ function DashboardHeader({ title = "Dashboard", subtitle }) {
};

return (
<header className="flex flex-col md:flex-row items-start md:items-center justify-between px-6 md:px-8 py-6 bg-transparent sticky top-0 z-150 gap-4 md:gap-0">
<header className="flex flex-col md:flex-row items-start md:items-center justify-between px-6 md:px-8 py-6 bg-transparent sticky top-0 z-100 gap-4 md:gap-0">
{/* Title + greeting */}
<div>
<h1 className="text-[28px] font-bold text-[#1a1a1a] m-0 tracking-tight">{title}</h1>
Expand All @@ -94,7 +94,7 @@ function DashboardHeader({ title = "Dashboard", subtitle }) {

{/* Notifications Dropdown Popover */}
{showDropdown && (
<div className="absolute right-0 top-[56px] w-80 sm:w-96 bg-white rounded-2xl shadow-2xl border border-gray-100 z-999 overflow-hidden flex flex-col animate-in fade-in slide-in-from-top-2 duration-200">
<div className="absolute right-0 top-[56px] w-80 sm:w-96 bg-white rounded-2xl shadow-2xl border border-gray-100 z-200 overflow-hidden flex flex-col animate-in fade-in slide-in-from-top-2 duration-200">
{/* Popover Header */}
<div className="px-5 py-3.5 border-b border-gray-100 flex items-center justify-between bg-gray-50/60">
<div className="flex items-center gap-2">
Expand Down
12 changes: 5 additions & 7 deletions src/components/Dashboard/IngredientsView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
useUploadIngredients,
useUpdateIngredientStock,
} from "../../hooks/dashboard/useIngredients";
import { useToast } from "../../store/toastStore";
import {
FiSearch,
FiUploadCloud,
Expand Down Expand Up @@ -176,11 +175,10 @@ function IngredientsView() {
const [viewingNutrientsItem, setViewingNutrientsItem] = useState(null);
const [selectedFile, setSelectedFile] = useState(null);

const { addToast } = useToast();

const { data: ingredients, isLoading, error, refetch } = useIngredients();
const { mutate: uploadFile, isPending: isUploading } = useUploadIngredients();
const { mutate: updateStock } = useUpdateIngredientStock();
const { mutate: uploadFile, isPending: isUploading, isSuccess: isUploaded } = useUploadIngredients();
const { mutate: updateStock } = useUpdateIngredientStock();

// ── File handlers ─────────────────────────────────────────────────────────
const handleFileSelect = (e) => {
Expand Down Expand Up @@ -430,12 +428,12 @@ function IngredientsView() {
{selectedFile && (
<button
onClick={handleFileSubmit}
disabled={isUploading}
disabled={isUploading || isUploaded}
className={`bg-[#F97316] hover:bg-orange-600 text-white px-6 py-2 rounded-full font-bold shadow-lg w-fit transition-transform hover:scale-105 ${
isUploading ? "opacity-50 cursor-not-allowed" : ""
isUploading || isUploaded ? "opacity-75 cursor-not-allowed" : ""
}`}
>
{isUploading ? "Uploading…" : "Submit"}
{isUploading ? "Uploading…" : isUploaded ? "Uploaded! ✓" : "Submit"}
</button>
)}
</div>
Expand Down
9 changes: 8 additions & 1 deletion src/components/Dashboard/InventoryAlerts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ function InventoryAlerts({ data }) {

return (
<div className="bg-white rounded-3xl px-6 py-6 shadow-sm border border-gray-100/60">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center justify-between mb-1">
<div className="flex items-center gap-2.5">
<FiAlertTriangle size={20} className="text-red-600" fill="#fee2e2" />
<h3 className="text-[14px] font-bold text-[#1a1a1a] m-0 uppercase tracking-wider">
Expand All @@ -39,6 +39,10 @@ function InventoryAlerts({ data }) {
</button>
)}
</div>
{/* Threshold hint */}
<p className="text-[11px] font-medium text-gray-400 mb-4">
Ingredients with stock below <span className="font-bold text-red-400">100g</span> or <span className="font-bold text-red-400">100ml</span> are flagged as low stock.
</p>

<div className="bg-white border border-[#FEE2E2] rounded-2xl p-4 shadow-[0_2px_10px_rgba(254,226,226,0.3)] flex flex-col justify-between min-h-[100px]">
<div className="flex gap-5 items-center overflow-x-auto py-2">
Expand Down Expand Up @@ -79,6 +83,9 @@ function InventoryAlerts({ data }) {
<h3 className="text-[18px] font-bold text-[#1a1a1a] m-0">
Low Stock Ingredients ({lowStockItems.length})
</h3>
<p className="text-[11px] font-medium text-gray-400 mt-0.5">
Below <span className="font-bold text-red-400">100g</span> or <span className="font-bold text-red-400">100ml</span>
</p>
</div>
<button
onClick={() => setIsModalOpen(false)}
Expand Down
8 changes: 4 additions & 4 deletions src/components/Dashboard/MenuManagementView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function MenuManagementView() {
}, []);

const { data: uploads, isLoading, error, refetch } = useMenuUploads();
const { mutate: uploadFile, isPending: isUploading } = useUploadMenu();
const { mutate: uploadFile, isPending: isUploading, isSuccess: isUploaded } = useUploadMenu();

const handleDrag = (e) => {
e.preventDefault();
Expand Down Expand Up @@ -157,10 +157,10 @@ function MenuManagementView() {
<button
type="button"
onClick={handleUpload}
disabled={isUploading}
className="mt-6 w-full py-3.5 rounded-2xl bg-[#F97316] text-white border-none font-bold text-[15px] cursor-pointer shadow-lg shadow-orange-500/30 hover:shadow-xl hover:shadow-orange-500/40 transition-all"
disabled={isUploading || isUploaded}
className="mt-6 w-full py-3.5 rounded-2xl bg-[#F97316] text-white border-none font-bold text-[15px] cursor-pointer shadow-lg shadow-orange-500/30 hover:shadow-xl hover:shadow-orange-500/40 transition-all disabled:opacity-75 disabled:cursor-not-allowed"
>
{isUploading ? "Uploading..." : "Process Import"}
{isUploading ? "Uploading..." : isUploaded ? "Imported! ✓" : "Process Import"}
</button>
)}
</div>
Expand Down
23 changes: 12 additions & 11 deletions src/components/Dashboard/RecipeBuilderView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { useLocation } from "react-router";
import DashboardHeader from "./DashboardHeader";
import { useCreateMenuItem, useUpdateMenuItem } from "../../hooks/dashboard/useMenuItems";
import { useIngredients } from "../../hooks/dashboard/useIngredients";
import { useToast } from "../../store/toastStore";
import {
FiPlus, FiTrash2, FiCamera, FiBookOpen, FiDollarSign,
FiAlignLeft, FiChevronDown, FiGrid, FiUploadCloud, FiSearch, FiX
Expand All @@ -24,10 +23,11 @@ export default function RecipeBuilderView() {
const [imageFile, setImageFile] = useState(null);
const [isCategoryOpen, setIsCategoryOpen] = useState(false);

const { addToast } = useToast();

const { data: availableIngredients = [] } = useIngredients();
const { mutate: createMeal, isSuccess: created, reset: resetCreate } = useCreateMenuItem();
const { mutate: updateMeal, isSuccess: updated, reset: resetUpdate } = useUpdateMenuItem();
const { mutate: createMeal, isSuccess: created, isPending: creating, isLoading: createLoading, reset: resetCreate } = useCreateMenuItem();
const { mutate: updateMeal, isSuccess: updated, isPending: updating, isLoading: updateLoading, reset: resetUpdate } = useUpdateMenuItem();
const isSaving = creating || createLoading || updating || updateLoading;
const saved = created || updated;

// Pre-fill form when editing an existing meal
Expand Down Expand Up @@ -107,7 +107,6 @@ export default function RecipeBuilderView() {
const payload = { ...(editMeal || {}), ...form, id: editMeal ? (editMeal.id || editMeal._id) : undefined, ingredients: localIngredients, imageFile };
const options = {
onSuccess: () => {
addToast(editMeal ? "Recipe updated successfully!" : "Recipe saved successfully!", "success");
setTimeout(() => {
if (!editMeal) {
setForm({ name: "", category: "", price: "", description: "" });
Expand All @@ -119,9 +118,6 @@ export default function RecipeBuilderView() {
resetUpdate();
}, 2000);
},
onError: () => {
addToast(editMeal ? "Failed to update recipe. Please try again." : "Failed to save recipe. Please try again.", "error");
}
};

if (editMeal) {
Expand Down Expand Up @@ -322,7 +318,7 @@ export default function RecipeBuilderView() {
<div className="col-span-1 lg:col-span-2 mt-2 mb-12 flex justify-center transition-all duration-300">
<div className="bg-white rounded-[2.5rem] p-10 flex flex-col items-center justify-center shadow-sm w-full max-w-2xl">
<h2 className="text-[18px] font-bold text-[#1a1a1a] mb-8">
{editMeal ? "Save your changes?" : "Do you want to Add this meal to Menu ?"}
{editMeal ? "Do you want to update this meal in Menu ?" : "Do you want to add this meal to Menu ?"}
</h2>
<div className="flex flex-col sm:flex-row items-center gap-6 w-full justify-center">
<button
Expand All @@ -337,9 +333,14 @@ export default function RecipeBuilderView() {
</button>
<button
onClick={handleSave}
className="cursor-pointer bg-orange-400 text-white font-bold py-3.5 px-12 rounded-2xl shadow-[0_4px_15px_rgba(249,115,22,0.3)] hover:bg-orange-600 transition-colors w-full sm:w-auto"
disabled={isSaving || saved}
className="cursor-pointer bg-orange-400 text-white font-bold py-3.5 px-12 rounded-2xl shadow-[0_4px_15px_rgba(249,115,22,0.3)] hover:bg-orange-600 transition-colors w-full sm:w-auto disabled:opacity-75 disabled:cursor-not-allowed"
>
{saved ? (editMeal ? "Saved!" : "Added!") : (editMeal ? "Save Changes" : "Add Meal")}
{isSaving
? (editMeal ? "Updating..." : "Adding...")
: saved
? (editMeal ? "Updated!" : "Added!")
: (editMeal ? "Update Meal" : "Add Meal")}
</button>
</div>
</div>
Expand Down
20 changes: 15 additions & 5 deletions src/components/Dashboard/shared/ConfirmModal.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { FiAlertTriangle } from "react-icons/fi";

function ConfirmModal({ isOpen, onClose, onConfirm, title, message, confirmLabel, confirmClassName }) {
function ConfirmModal({ isOpen, onClose, onConfirm, title, message, confirmLabel, confirmClassName, isLoading, isSuccess }) {
if (!isOpen) return null;

const baseLabel = confirmLabel || "Delete";
const buttonLabel = isLoading
? `${baseLabel}...`
: isSuccess
? "Done!"
: baseLabel;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
<div className="bg-white rounded-3xl w-full max-w-sm shadow-2xl overflow-hidden animate-slide-in">
Expand All @@ -19,22 +26,24 @@ function ConfirmModal({ isOpen, onClose, onConfirm, title, message, confirmLabel
<div className="mt-4 flex gap-3 w-full">
<button
onClick={onClose}
className="flex-1 py-3 rounded-xl font-bold text-gray-600 bg-gray-100 hover:bg-gray-200 transition-colors"
disabled={isLoading}
className="flex-1 py-3 rounded-xl font-bold text-gray-600 bg-gray-100 hover:bg-gray-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
Cancel
</button>
<button
onClick={async () => {
try {
await onConfirm?.();
onClose();
if (!isLoading) onClose();
} catch {
// Keep modal open so the user can retry or see error feedback
}
}}
className={`flex-1 py-3 rounded-xl font-bold text-white transition-colors ${confirmClassName || "bg-red-500 hover:bg-red-600 shadow-lg shadow-red-500/30"}`}
disabled={isLoading || isSuccess}
className={`flex-1 py-3 rounded-xl font-bold text-white transition-colors disabled:opacity-75 disabled:cursor-not-allowed ${confirmClassName || "bg-red-500 hover:bg-red-600 shadow-lg shadow-red-500/30"}`}
>
{confirmLabel || "Delete"}
{buttonLabel}
</button>
</div>
</div>
Expand All @@ -44,3 +53,4 @@ function ConfirmModal({ isOpen, onClose, onConfirm, title, message, confirmLabel
}

export default ConfirmModal;

36 changes: 26 additions & 10 deletions src/components/Dashboard/shared/InactiveMenuModal.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import React, { useRef, useState } from "react";
import { FiX, FiCamera, FiUploadCloud } from "react-icons/fi";
import { useUpdateMenuItem } from "../../../hooks/dashboard/useMenuItems";
import { useToast } from "../../../store/toastStore";


export default function InactiveMenuModal({ isOpen, onClose, inactiveItems }) {
const { addToast } = useToast();
const { mutate: updateMeal, isPending } = useUpdateMenuItem();
const fileInputRef = useRef(null);
const [selectedItemId, setSelectedItemId] = useState(null);
// Tracks which item is actively uploading — persists across modal open/close cycles
const [uploadingItemId, setUploadingItemId] = useState(null);
const [uploadedItemId, setUploadedItemId] = useState(null);

if (!isOpen) return null;

const handleAddPhotoClick = (id) => {
// Do not allow switching items while an upload is in progress
if (isPending) return;
if (isPending || uploadingItemId) return;
setSelectedItemId(id);
if (fileInputRef.current) {
fileInputRef.current.click();
Expand All @@ -25,18 +27,30 @@ export default function InactiveMenuModal({ isOpen, onClose, inactiveItems }) {
if (file && selectedItemId) {
const itemToUpdate = inactiveItems.find((i) => i.id === selectedItemId);
if (itemToUpdate) {
const uploadId = selectedItemId;
setSelectedItemId(null);
setUploadingItemId(uploadId); // Keep track of which item is uploading
setUploadedItemId(null);
if (fileInputRef.current) fileInputRef.current.value = "";
if (inactiveItems.length === 1) onClose(); // Auto close immediately if last item

updateMeal({
id: itemToUpdate.id || itemToUpdate._id,
data: { ...itemToUpdate, imageFile: file },
});
updateMeal(
{ id: itemToUpdate.id || itemToUpdate._id, data: { ...itemToUpdate, imageFile: file } },
{
onSuccess: () => {
setUploadingItemId(null);
setUploadedItemId(uploadId); // Mark this item as uploaded
},
onError: () => {
setUploadingItemId(null); // Reset on failure so user can retry
},
}
);
}
}
};


return (
<div className="fixed inset-0 z-100 flex items-center justify-center p-4">
{/* Backdrop */}
Expand Down Expand Up @@ -104,11 +118,13 @@ export default function InactiveMenuModal({ isOpen, onClose, inactiveItems }) {

<button
onClick={() => handleAddPhotoClick(item.id)}
disabled={isPending && selectedItemId === item.id}
className="flex items-center gap-2 px-4 py-2 bg-orange-50 hover:bg-orange-100 text-orange-600 rounded-xl text-[13px] font-bold transition-colors shadow-sm disabled:opacity-50 disabled:cursor-not-allowed"
disabled={uploadingItemId === item.id || uploadedItemId === item.id}
className="flex items-center gap-2 px-4 py-2 bg-orange-50 hover:bg-orange-100 text-orange-600 rounded-xl text-[13px] font-bold transition-colors shadow-sm disabled:opacity-75 disabled:cursor-not-allowed"
>
{isPending && selectedItemId === item.id ? (
{uploadingItemId === item.id ? (
<span className="animate-pulse">Uploading...</span>
) : uploadedItemId === item.id ? (
<span className="text-green-600">Photo Added! ✓</span>
) : (
<>
<FiUploadCloud size={16} />
Expand Down
13 changes: 9 additions & 4 deletions src/components/Dashboard/shared/IngredientModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const validateStock = (v) => {
* Updates a single ingredient's stock via PATCH /api/ingredients/{id}/stock.
* Calls onSubmit({ stock: number }) when the form is valid.
*/
function IngredientModal({ isOpen, onClose, onSubmit, initialData }) {
function IngredientModal({ isOpen, onClose, onSubmit, initialData, isPending, isSuccess }) {
const [stock, setStock] = useState("");
const [error, setError] = useState("");

Expand Down Expand Up @@ -49,6 +49,8 @@ function IngredientModal({ isOpen, onClose, onSubmit, initialData }) {
? "border-red-400 focus:border-red-400"
: "border-gray-200 focus:border-orange-400";

const submitLabel = isPending ? "Saving..." : isSuccess ? "Saved!" : "Save Changes";

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm p-4">
<div className="bg-white rounded-3xl w-full max-w-sm shadow-2xl overflow-hidden">
Expand Down Expand Up @@ -97,15 +99,17 @@ function IngredientModal({ isOpen, onClose, onSubmit, initialData }) {
<button
type="button"
onClick={onClose}
className="px-5 py-2.5 rounded-xl font-bold text-gray-500 hover:bg-gray-100 transition-colors"
disabled={isPending}
className="px-5 py-2.5 rounded-xl font-bold text-gray-500 hover:bg-gray-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
Cancel
</button>
<button
type="submit"
className="px-5 py-2.5 rounded-xl font-bold text-white bg-orange-500 hover:bg-orange-600 transition-colors shadow-lg shadow-orange-500/30"
disabled={isPending || isSuccess}
className="px-5 py-2.5 rounded-xl font-bold text-white bg-orange-500 hover:bg-orange-600 transition-colors shadow-lg shadow-orange-500/30 disabled:opacity-75 disabled:cursor-not-allowed"
>
Save Changes
{submitLabel}
</button>
</div>
</form>
Expand All @@ -116,3 +120,4 @@ function IngredientModal({ isOpen, onClose, onSubmit, initialData }) {
}

export default IngredientModal;

Loading