+
+ {/* Header */}
+
+
+
+ {/* Welcome & Stats */}
+
+
+
Welcome, {user.displayName?.split(' ')[0]}
+
What's cooking today?
+
+
+
setIsGenerateModalOpen(true)} className="dark:bg-stone-800 dark:text-stone-200 dark:hover:bg-stone-700">
+ AI Recipe
+
+
setIsImportModalOpen(true)} className="dark:bg-stone-800 dark:text-stone-200 dark:hover:bg-stone-700">
+ Import URL
+
+
{ setEditingRecipe(null); setIsAddModalOpen(true); }} className="dark:bg-stone-100 dark:text-stone-900 dark:hover:bg-stone-200">
+ Add Recipe
+
+
+
+
+ {/* Filters & Search */}
+
+
+ {/* Recipe Grid */}
+
+
+ {filteredRecipes.map((recipe) => (
+ setViewingRecipe(recipe)}
+ className="group cursor-pointer"
+ >
+
+
+ {recipe.imageUrl ? (
+
+ ) : (
+
+
+
+ )}
+
+ {recipe.category}
+
+
+
+
+
{recipe.title}
+
+
+ {recipe.rating || 0}
+
+
+
+
+
+ {recipe.estimatedTime ? `${recipe.estimatedTime}m` : `${recipe.instructions.length * 5}m`}
+
+
+
+ {recipe.ingredients.length} ingredients
+
+
+
+
+
+ ))}
+
+
+
+ {filteredRecipes.length === 0 && (
+
+
+
+
+
No recipes found
+
Try adjusting your search or filters.
+
+ )}
+
+
+
+ {/* Modals */}
+
+ {/* View Recipe Modal */}
+
{ setViewingRecipe(null); setIsDeleteConfirmOpen(false); }}
+ title={viewingRecipe?.title}
+ >
+
+
+
{viewingRecipe?.category}
+
+
+ {viewingRecipe?.rating}
+
+ {viewingRecipe?.sourceUrl && (
+
+ Source
+
+ )}
+
+
+ {viewingRecipe?.imageUrl && (
+
+
+
+ )}
+
+
+
+
Ingredients
+
+ {viewingRecipe?.ingredients.map((ing, i) => (
+
+
+ {ing}
+
+ ))}
+
+
+
+
Instructions
+
+ {viewingRecipe?.instructions.map((step, i) => (
+
+ {i + 1}
+ {step}
+
+ ))}
+
+
+
+
+
+ {isDeleteConfirmOpen ? (
+
+ Are you sure?
+ viewingRecipe && handleDeleteRecipe(viewingRecipe.id!)}>
+ Yes, Delete
+
+ setIsDeleteConfirmOpen(false)}>
+ Cancel
+
+
+ ) : (
+
setIsDeleteConfirmOpen(true)}>
+ Delete
+
+ )}
+
{ setEditingRecipe(viewingRecipe); setViewingRecipe(null); setIsAddModalOpen(true); }}>
+ Edit Recipe
+
+
+
+
+
+ {/* Add/Edit Recipe Modal */}
+
{ setIsAddModalOpen(false); setEditingRecipe(null); setRecipeFormError(null); }}
+ title={editingRecipe?.id ? "Edit Recipe" : "Add New Recipe"}
+ >
+
+
+
+ {/* Import Modal */}
+
{ setIsImportModalOpen(false); setImportError(null); }} title="Import from Web">
+
+
+ Recipe URL
+ { setImportUrl(e.target.value); setImportError(null); }}
+ className="w-full px-4 py-3 rounded-xl border border-stone-200 dark:border-stone-800 bg-white dark:bg-stone-900 text-stone-800 dark:text-stone-100 focus:ring-2 focus:ring-stone-800/10 dark:focus:ring-stone-100/10 outline-none"
+ />
+
+ {importError && (
+
+ )}
+
+ {isProcessing ? <> Extracting...> : "Import Recipe"}
+
+
Gemini will intelligently gather only the essential recipe details and ingredients for you.
+
+
+
+ {/* Generate AI Recipe Modal */}
+
setIsGenerateModalOpen(false)} title="Generate AI Recipe">
+
+
+ Meal Type
+ setAiCategory(e.target.value as Category)}
+ className="w-full px-4 py-3 rounded-xl border border-stone-200 dark:border-stone-800 bg-white dark:bg-stone-900 text-stone-800 dark:text-stone-100 focus:ring-2 focus:ring-stone-800/10 dark:focus:ring-stone-100/10 outline-none"
+ >
+ {['Breakfast', 'Lunch', 'Dinner', 'Dessert', 'Snack', 'Drink', 'Other'].map(c => {c} )}
+
+
+
+ Preferences (Optional)
+ setAiDetails(e.target.value)}
+ rows={3}
+ className="w-full px-4 py-3 rounded-xl border border-stone-200 dark:border-stone-800 bg-white dark:bg-stone-900 text-stone-800 dark:text-stone-100 focus:ring-2 focus:ring-stone-800/10 dark:focus:ring-stone-100/10 outline-none"
+ />
+
+
+ {isProcessing ? <> Generating...> : "Generate Recipe & Image"}
+
+
Gemini will create a unique recipe and generate a photo to match.
+
+
+
+ {/* Household Modal */}
+
setIsHouseholdModalOpen(false)} title="My Households">
+
+
+
Switch Household
+
+ {households.map(h => (
+ { setSelectedHousehold(h); setIsHouseholdModalOpen(false); }}
+ className={cn(
+ "flex items-center justify-between p-4 rounded-2xl border transition-all",
+ selectedHousehold?.id === h.id
+ ? "bg-stone-800 dark:bg-stone-100 border-stone-800 dark:border-stone-100 text-stone-50 dark:text-stone-900"
+ : "bg-white dark:bg-stone-800 border-stone-200 dark:border-stone-700 hover:border-stone-400 dark:hover:border-stone-500 text-stone-900 dark:text-stone-100"
+ )}
+ >
+ {h.name}
+ {selectedHousehold?.id === h.id && }
+
+ ))}
+
+
+
+ {selectedHousehold && selectedHousehold.ownerId === user.uid && (
+
+
Invite Member
+
Enter the User ID of the person you want to invite.
+
{
+ e.preventDefault();
+ const uid = new FormData(e.currentTarget).get('uid') as string;
+ handleAddMember(uid);
+ e.currentTarget.reset();
+ }} className="flex gap-2">
+
+ Invite
+
+
+ {Object.entries(selectedHousehold.members).map(([uid, role]) => (
+
+
{uid.slice(0, 8)}...
+
+ {role}
+ {uid !== user.uid && (
+ handleRemoveMember(uid)}
+ className="text-stone-400 hover:text-red-500 transition-colors"
+ title="Remove member"
+ >
+
+
+ )}
+
+
+ ))}
+
+
+ )}
+
+
+
My User ID
+
+ {user.uid}
+
+ {copied ? 'Copied!' : 'Copy'}
+
+
+
+
+
+
Create New Household
+ {
+ e.preventDefault();
+ const name = new FormData(e.currentTarget).get('name') as string;
+ handleCreateHousehold(name);
+ }} className="flex gap-2">
+
+
+ {isProcessing ? <> Creating...> : "Create"}
+
+
+
+
+ {selectedHousehold && selectedHousehold.ownerId === user.uid && (
+
+ {isDeleteHouseholdConfirmOpen ? (
+
+ Are you sure?
+ handleDeleteHousehold(selectedHousehold.id)}>
+ Yes, Delete Household
+
+ setIsDeleteHouseholdConfirmOpen(false)}>
+ Cancel
+
+
+ ) : (
+
setIsDeleteHouseholdConfirmOpen(true)}>
+ Delete Household
+
+ )}
+
+ )}
+
+
+
+ {/* Demo Disabled Modal */}
+
setIsDemoDisabledModalOpen(false)} title="Feature Disabled">
+
+
+
+
+
+ Sharing households with other users has been disabled for this demo to protect user privacy.
+
+
+ If you'd like to use this feature, click the Remix button in AI Studio to create your own private version of this app!
+
+
setIsDemoDisabledModalOpen(false)}>
+ Got it
+
+
+
+
+ {/* Data Deleted Modal */}
+
setIsDataDeletedModalOpen(false)} title="Data Cleanup">
+
+
+
+
+
+ Welcome back! Just a heads up: to keep this demo clean, all user data is automatically deleted after 24 hours.
+
+
+ If you want your recipes to persist forever, click the Remix button in AI Studio to create your own private version of this app!
+
+
setIsDataDeletedModalOpen(false)}>
+ I understand
+
+
+
+
+ {/* First Family Created Modal */}
+
setIsFirstFamilyModalOpen(false)} title="Welcome to Heirloom!">
+
+
+
+
+
+
+ Your family kitchen has been created!
+
+
+
Important Demo Info:
+
To keep this demo fast and clean, all data is automatically deleted after 24 hours.
+
+
+ If you want to create your own permanent version and keep your family recipes forever, click the Remix button in the top right of AI Studio!
+
+
+
setIsFirstFamilyModalOpen(false)}>
+ Start Cooking
+
+
+
+
+