+ {/* Header */}
+
+
+
Edit Recipe
+ {title || 'Untitled Recipe'}
+
+
+
+
+
+
+
+
+
+
+
+ {/* Unsaved changes banner */}
+ {hasChanges && (
+
+ ⚠️ You have unsaved changes. Click "Save All Changes" to persist them.
+
+ )}
+
+ {/* Collapsible Basic Info Section */}
+
+
setBasicInfoCollapsed(!basicInfoCollapsed)}
+ >
+
Recipe Details
+
+ ▼
+
+
+
+ {!basicInfoCollapsed && (
+
+
+
+ {
+ setTitle(e.target.value);
+ setHasChanges(true);
+ }}
+ required
+ />
+
+
+
+
+
+
+
+
+
+
+ {/* Image Upload */}
+
+
+
+ {recipe.imageUrl && (
+
+

+
+
Current main image
+
+
+
+ )}
+
+
+
+
+
+
+ {imageError &&
{imageError}
}
+
+
+ Supported formats: JPG, PNG, GIF, WEBP. Max size: 20MB
+
+
+
+ {/* Section mode toggle - disabled for now, only support simple mode */}
+
+
+
+
+ )}
+
+
+ {/* Two-Column Layout: Ingredients + Instructions */}
+ {!useSections && (
+
+ {/* Left Column: Ingredients Panel (Sticky) */}
+
+
Ingredients
+
+
+
+
+
+
+
+
+ {/* Bulk Actions */}
+ {selectedIngredients.size > 0 && allInstructions.length > 0 && (
+
+
+ Map {selectedIngredients.size} selected to:
+
+
+
+
+ )}
+
+
+ {/* Right Column: Instructions Panel */}
+
+
Instructions
+
+
+ {allInstructions.map((instruction, index) => {
+ const isEditing = editingInstructionId === instruction.id;
+ const mappedIngredients = getMappedIngredientsForInstruction(
+ instruction.id || ''
+ );
+ const isDragOver = dragOverInstructionId === instruction.id;
+
+ return (
+ - handleInstructionDragOver(e, instruction.id || '')}
+ onDragLeave={handleInstructionDragLeave}
+ onDrop={(e) => handleInstructionDrop(e, instruction.id || '')}
+ >
+
+
Step {instruction.step}
+
+ {!isEditing && (
+
+
+
+
+ )}
+
+
+ {isEditing ? (
+ <>
+ setEditingInstructionTiming(e.target.value)}
+ placeholder="Timing (optional, e.g., 8:00am)"
+ />
+
+ );
+ })}
+
+
+
+
+
+ )}
+
+ {/* TODO: Add section support later */}
+ {useSections && (
+
+
Multi-section recipe editing coming soon!
+
For now, please uncheck "Multi-section recipe" above.
+
+ )}
+
+ {/* Footer with Save/Cancel buttons */}
+
+
+
+
+
+ );
+}
+
+export default UnifiedEditRecipe;
diff --git a/packages/web/src/styles/UnifiedRecipeEdit.css b/packages/web/src/styles/UnifiedRecipeEdit.css
new file mode 100644
index 0000000..a93a311
--- /dev/null
+++ b/packages/web/src/styles/UnifiedRecipeEdit.css
@@ -0,0 +1,861 @@
+/* Unified Recipe Edit - All-in-One Interface */
+
+.unified-recipe-edit {
+ background-color: #fafafa;
+ min-height: 100vh;
+ padding: 1.5rem;
+ font-size: 1.05rem;
+}
+
+/* Header */
+.unified-header {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+ padding-bottom: 1.5rem;
+ border-bottom: 3px solid #1976d2;
+}
+
+.unified-title h1 {
+ font-size: 2rem;
+ margin: 0;
+ color: #1976d2;
+ line-height: 1.2;
+}
+
+.unified-title h2 {
+ font-size: 1.5rem;
+ margin: 0.5rem 0 0 0;
+ color: #555;
+ font-weight: normal;
+}
+
+.unified-controls {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ align-items: center;
+}
+
+.unified-controls button {
+ padding: 0.75rem 1.5rem;
+ font-size: 1rem;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ background-color: #1976d2;
+ color: white;
+ font-weight: 500;
+ transition: background-color 0.2s;
+}
+
+.unified-controls button:hover:not(:disabled) {
+ background-color: #0d47a1;
+}
+
+.unified-controls button:disabled {
+ background-color: #ccc;
+ cursor: not-allowed;
+}
+
+.unified-controls .save-btn {
+ background-color: #2e7d32;
+}
+
+.unified-controls .save-btn:hover:not(:disabled) {
+ background-color: #1b5e20;
+}
+
+.unified-controls .regenerate-btn {
+ background-color: #ff9800;
+}
+
+.unified-controls .regenerate-btn:hover:not(:disabled) {
+ background-color: #f57c00;
+}
+
+.unified-controls .cancel-btn {
+ background-color: #757575;
+}
+
+.unified-controls .cancel-btn:hover {
+ background-color: #424242;
+}
+
+/* Unsaved changes banner */
+.unsaved-changes-banner {
+ background-color: #fff3e0;
+ border: 2px solid #ff9800;
+ border-radius: 8px;
+ padding: 1rem 1.5rem;
+ margin-bottom: 1.5rem;
+ font-weight: 500;
+ color: #e65100;
+}
+
+/* Collapsible Basic Info Section */
+.basic-info-section {
+ background-color: white;
+ border: 2px solid #1976d2;
+ border-radius: 8px;
+ margin-bottom: 1.5rem;
+ overflow: hidden;
+}
+
+.basic-info-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0.75rem 1rem;
+ background-color: #e3f2fd;
+ cursor: pointer;
+ user-select: none;
+}
+
+.basic-info-header:hover {
+ background-color: #bbdefb;
+}
+
+.basic-info-header h3 {
+ margin: 0;
+ font-size: 1.1rem;
+ color: #1976d2;
+}
+
+.collapse-icon {
+ font-size: 1.2rem;
+ color: #1976d2;
+ transition: transform 0.3s;
+}
+
+.collapse-icon.collapsed {
+ transform: rotate(-90deg);
+}
+
+.basic-info-content {
+ padding: 1rem;
+}
+
+.form-group {
+ margin-bottom: 0.75rem;
+}
+
+.form-group label {
+ display: block;
+ font-weight: 500;
+ margin-bottom: 0.25rem;
+ color: #333;
+ font-size: 0.9rem;
+}
+
+.form-group input[type="text"],
+.form-group input[type="number"],
+.form-group textarea {
+ width: 100%;
+ padding: 0.5rem;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ font-size: 0.95rem;
+ font-family: inherit;
+}
+
+.form-group textarea {
+ min-height: 60px;
+ resize: vertical;
+}
+
+.form-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
+ gap: 0.75rem;
+ margin-bottom: 0.75rem;
+}
+
+.checkbox-label {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ cursor: pointer;
+ font-weight: normal;
+}
+
+.checkbox-label input[type="checkbox"] {
+ width: 1.25rem;
+ height: 1.25rem;
+ cursor: pointer;
+}
+
+/* Image Upload Section */
+.image-upload-section {
+ padding: 0.75rem;
+ background-color: #f5f5f5;
+ border-radius: 6px;
+}
+
+.current-image {
+ display: flex;
+ gap: 0.75rem;
+ align-items: flex-start;
+ margin-bottom: 0.5rem;
+}
+
+.current-image img {
+ max-width: 200px;
+ border-radius: 6px;
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+}
+
+.image-actions {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.image-note {
+ color: #666;
+ font-size: 0.9rem;
+ margin: 0;
+}
+
+.btn-delete-image {
+ padding: 0.5rem 1rem;
+ background-color: #d32f2f;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+}
+
+.btn-delete-image:hover:not(:disabled) {
+ background-color: #b71c1c;
+}
+
+.file-input {
+ display: none;
+}
+
+.file-input-label {
+ display: inline-block;
+ padding: 0.5rem 1rem;
+ background-color: #1976d2;
+ color: white;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ border: none;
+ font-weight: 500;
+ transition: background-color 0.2s;
+}
+
+.file-input-label:hover {
+ background-color: #0d47a1;
+}
+
+.image-upload-control {
+ display: inline-block;
+}
+
+.image-help-text {
+ font-size: 0.85rem;
+ color: #666;
+ margin-top: 0.5rem;
+}
+
+/* Two-Column Main Layout */
+.edit-content {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 2rem;
+ align-items: start;
+}
+
+/* Left Column: Ingredients Panel (Sticky) */
+.ingredients-panel {
+ position: sticky;
+ top: 1.5rem;
+ background-color: white;
+ padding: 1.5rem;
+ border-radius: 12px;
+ border: 2px solid #2e7d32;
+ max-height: calc(100vh - 10rem);
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+.ingredients-panel h3 {
+ margin-top: 0;
+ font-size: 1.5rem;
+ color: #2e7d32;
+ margin-bottom: 1rem;
+}
+
+.ingredients-list {
+ list-style: none;
+ padding: 0;
+ margin: 0 0 1rem 0;
+ flex: 1;
+ overflow-y: auto;
+ min-height: 0;
+}
+
+.ingredient-item {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.75rem;
+ border-radius: 4px;
+ transition: background-color 0.2s;
+ border-bottom: 1px solid #e0e0e0;
+ margin-bottom: 0.5rem;
+}
+
+.ingredient-item:hover {
+ background-color: #e8f5e9;
+}
+
+.ingredient-item.selected {
+ background-color: #c8e6c9;
+ border: 2px solid #2e7d32;
+}
+
+.ingredient-checkbox {
+ width: 1.25rem;
+ height: 1.25rem;
+ cursor: pointer;
+}
+
+.drag-handle {
+ color: #9e9e9e;
+ font-size: 1rem;
+ cursor: grab;
+ user-select: none;
+}
+
+.ingredient-item.dragging {
+ opacity: 0.5;
+ cursor: grabbing;
+}
+
+.ingredient-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.ingredient-inputs {
+ display: grid;
+ grid-template-columns: 60px 60px 1fr;
+ gap: 0.25rem;
+}
+
+.ingredient-inputs input {
+ padding: 0.25rem 0.5rem;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ font-size: 0.9rem;
+}
+
+.ingredient-display {
+ font-size: 1.05rem;
+ cursor: pointer;
+}
+
+.ingredient-display:hover {
+ color: #1b5e20;
+}
+
+.btn-remove-ingredient {
+ background: none;
+ border: none;
+ color: #d32f2f;
+ font-size: 1.25rem;
+ cursor: pointer;
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+ transition: background-color 0.2s;
+ line-height: 1;
+}
+
+.btn-remove-ingredient:hover {
+ background-color: #ffebee;
+}
+
+.ingredient-actions {
+ display: flex;
+ gap: 0.5rem;
+ margin-bottom: 1rem;
+ flex-shrink: 0;
+}
+
+.btn-add-ingredient,
+.btn-delete-selected {
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ font-weight: 500;
+}
+
+.btn-add-ingredient {
+ background-color: #2e7d32;
+ color: white;
+}
+
+.btn-add-ingredient:hover {
+ background-color: #1b5e20;
+}
+
+.btn-delete-selected {
+ background-color: #d32f2f;
+ color: white;
+}
+
+.btn-delete-selected:hover:not(:disabled) {
+ background-color: #b71c1c;
+}
+
+.btn-delete-selected:disabled {
+ background-color: #ccc;
+ cursor: not-allowed;
+}
+
+.bulk-actions {
+ margin-top: 1rem;
+ padding-top: 1rem;
+ border-top: 2px solid #e0e0e0;
+ flex-shrink: 0;
+}
+
+.bulk-actions p {
+ font-size: 0.9rem;
+ color: #666;
+ margin: 0 0 0.5rem 0;
+}
+
+.bulk-actions select {
+ width: 100%;
+ padding: 0.5rem;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ font-size: 0.9rem;
+ margin-bottom: 0.5rem;
+}
+
+.bulk-actions button {
+ width: 100%;
+ padding: 0.5rem 1rem;
+ background-color: #1976d2;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+}
+
+.bulk-actions button:hover:not(:disabled) {
+ background-color: #0d47a1;
+}
+
+.bulk-actions button:disabled {
+ background-color: #ccc;
+ cursor: not-allowed;
+}
+
+/* Right Column: Instructions Panel */
+.instructions-panel {
+ background-color: transparent;
+}
+
+.instructions-panel h3 {
+ font-size: 1.75rem;
+ color: #1976d2;
+ margin-bottom: 1.5rem;
+ margin-top: 0;
+}
+
+.instructions-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.instruction-item {
+ background-color: white;
+ padding: 1.5rem;
+ border-radius: 12px;
+ margin-bottom: 1.5rem;
+ border: 2px solid #e0e0e0;
+ transition: all 0.3s ease;
+}
+
+.instruction-item:hover {
+ border-color: #1976d2;
+}
+
+.instruction-item.drag-over {
+ border-color: #2e7d32;
+ background-color: #f1f8e9;
+ box-shadow: 0 4px 12px rgba(46, 125, 50, 0.2);
+}
+
+.instruction-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.step-number {
+ font-size: 1.3rem;
+ font-weight: 700;
+ color: #1976d2;
+}
+
+.instruction-controls {
+ display: flex;
+ gap: 0.5rem;
+}
+
+.btn-edit-instruction,
+.btn-delete-instruction {
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+ font-weight: 500;
+}
+
+.btn-edit-instruction {
+ background-color: #1976d2;
+ color: white;
+}
+
+.btn-edit-instruction:hover {
+ background-color: #0d47a1;
+}
+
+.btn-delete-instruction {
+ background-color: #d32f2f;
+ color: white;
+}
+
+.btn-delete-instruction:hover {
+ background-color: #b71c1c;
+}
+
+.instruction-timing-input {
+ width: 100%;
+ padding: 0.5rem;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ font-size: 0.9rem;
+ margin-bottom: 0.5rem;
+}
+
+.instruction-timing-display {
+ font-size: 1rem;
+ color: #d84315;
+ background-color: #fff3e0;
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ font-weight: 500;
+ display: inline-block;
+}
+
+.instruction-text-display {
+ font-size: 1.2rem;
+ line-height: 1.8;
+ color: #212121;
+ margin-bottom: 1rem;
+ cursor: pointer;
+ padding: 0.5rem;
+ border-radius: 4px;
+}
+
+.instruction-text-display:hover {
+ background-color: #f5f5f5;
+}
+
+.instruction-text-input {
+ width: 100%;
+ min-height: 80px;
+ padding: 0.75rem;
+ border: 2px solid #1976d2;
+ border-radius: 4px;
+ font-size: 1.1rem;
+ font-family: inherit;
+ resize: vertical;
+ margin-bottom: 0.5rem;
+}
+
+.instruction-edit-actions {
+ display: flex;
+ gap: 0.5rem;
+ margin-bottom: 1rem;
+}
+
+.btn-save-instruction,
+.btn-cancel-instruction {
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+}
+
+.btn-save-instruction {
+ background-color: #2e7d32;
+ color: white;
+}
+
+.btn-save-instruction:hover {
+ background-color: #1b5e20;
+}
+
+.btn-cancel-instruction {
+ background-color: #757575;
+ color: white;
+}
+
+.btn-cancel-instruction:hover {
+ background-color: #424242;
+}
+
+.drop-zone {
+ background-color: #f1f8e9;
+ padding: 1rem 1.25rem;
+ border-radius: 8px;
+ margin-top: 1rem;
+ border-left: 4px solid #2e7d32;
+ min-height: 60px;
+}
+
+.drop-zone-header {
+ color: #1b5e20;
+ font-size: 1.05rem;
+ font-weight: 500;
+ display: block;
+ margin-bottom: 0.5rem;
+}
+
+.mapped-ingredients-list {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.mapped-ingredient-item {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+ padding: 0.5rem;
+ border-radius: 4px;
+ transition: background-color 0.2s;
+ margin-bottom: 0.25rem;
+}
+
+.mapped-ingredient-item:hover {
+ background-color: #dcedc8;
+}
+
+.mapped-ingredient-text {
+ flex: 1;
+ font-size: 1.05rem;
+}
+
+.no-ingredients-mapped {
+ color: #757575;
+ font-style: italic;
+ margin: 0.5rem 0;
+ text-align: center;
+ padding: 1rem;
+}
+
+.btn-add-instruction {
+ padding: 0.75rem 1.5rem;
+ background-color: #1976d2;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 500;
+ margin-top: 1rem;
+}
+
+.btn-add-instruction:hover {
+ background-color: #0d47a1;
+}
+
+/* Section Support */
+.section-container {
+ margin-bottom: 3rem;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+ padding: 1rem;
+ background-color: #e3f2fd;
+ border-radius: 8px;
+ border: 2px solid #1976d2;
+}
+
+.section-header h4 {
+ font-size: 1.5rem;
+ margin: 0;
+ color: #0d47a1;
+}
+
+.section-timing {
+ font-size: 1rem;
+ color: #666;
+ background-color: #fff3e0;
+ padding: 0.5rem 1rem;
+ border-radius: 6px;
+ font-weight: 500;
+}
+
+.btn-remove-section {
+ padding: 0.5rem 1rem;
+ background-color: #d32f2f;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 0.9rem;
+}
+
+.btn-remove-section:hover {
+ background-color: #b71c1c;
+}
+
+.btn-add-section {
+ padding: 0.75rem 1.5rem;
+ background-color: #1976d2;
+ color: white;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-size: 1rem;
+ font-weight: 500;
+ margin-top: 2rem;
+}
+
+.btn-add-section:hover {
+ background-color: #0d47a1;
+}
+
+/* Loading and Error States */
+.loading,
+.error {
+ text-align: center;
+ padding: 3rem;
+ font-size: 1.2rem;
+}
+
+.error {
+ color: #d32f2f;
+}
+
+/* Footer Save Button */
+.unified-footer {
+ position: sticky;
+ bottom: 0;
+ background-color: white;
+ border-top: 3px solid #1976d2;
+ padding: 1rem 1.5rem;
+ margin-top: 2rem;
+ display: flex;
+ justify-content: center;
+ gap: 1rem;
+ box-shadow: 0 -2px 8px rgba(0,0,0,0.1);
+ z-index: 10;
+}
+
+.unified-footer button {
+ padding: 0.75rem 2rem;
+ font-size: 1.1rem;
+ border: none;
+ border-radius: 8px;
+ cursor: pointer;
+ font-weight: 600;
+ transition: background-color 0.2s;
+}
+
+.unified-footer .save-btn-large {
+ background-color: #2e7d32;
+ color: white;
+}
+
+.unified-footer .save-btn-large:hover:not(:disabled) {
+ background-color: #1b5e20;
+}
+
+.unified-footer .save-btn-large:disabled {
+ background-color: #ccc;
+ cursor: not-allowed;
+}
+
+.unified-footer .cancel-btn-large {
+ background-color: #757575;
+ color: white;
+}
+
+.unified-footer .cancel-btn-large:hover {
+ background-color: #424242;
+}
+
+/* Responsive Design */
+@media (max-width: 1024px) {
+ .edit-content {
+ grid-template-columns: 300px 1fr;
+ gap: 1rem;
+ }
+
+ .ingredients-panel {
+ font-size: 0.95rem;
+ }
+}
+
+@media (max-width: 768px) {
+ .unified-recipe-edit {
+ padding: 1rem;
+ }
+
+ .unified-title h1 {
+ font-size: 1.75rem;
+ }
+
+ .unified-controls {
+ flex-direction: column;
+ width: 100%;
+ }
+
+ .unified-controls button {
+ width: 100%;
+ }
+
+ .edit-content {
+ grid-template-columns: 1fr;
+ gap: 1.5rem;
+ }
+
+ .ingredients-panel {
+ position: static;
+ max-height: none;
+ }
+
+ .form-row {
+ grid-template-columns: 1fr;
+ }
+}