All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 13m3s
80 lines
3.4 KiB
TypeScript
80 lines
3.4 KiB
TypeScript
// src/components/RecipeSuggester.tsx
|
|
import React, { useState, useCallback } from 'react';
|
|
import { suggestRecipe } from '../services/apiClient';
|
|
import { logger } from '../services/logger.client';
|
|
|
|
export const RecipeSuggester: React.FC = () => {
|
|
const [ingredients, setIngredients] = useState<string>('');
|
|
const [suggestion, setSuggestion] = useState<string | null>(null);
|
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const handleSubmit = useCallback(async (event: React.FormEvent<HTMLFormElement>) => {
|
|
event.preventDefault();
|
|
setIsLoading(true);
|
|
setError(null);
|
|
setSuggestion(null);
|
|
|
|
const ingredientList = ingredients.split(',').map(item => item.trim()).filter(Boolean);
|
|
|
|
if (ingredientList.length === 0) {
|
|
setError('Please enter at least one ingredient.');
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await suggestRecipe(ingredientList);
|
|
const data = await response.json();
|
|
|
|
if (!response.ok) {
|
|
throw new Error(data.message || 'Failed to get suggestion.');
|
|
}
|
|
|
|
setSuggestion(data.suggestion);
|
|
} catch (err) {
|
|
const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred.';
|
|
logger.error({ error: err }, 'Failed to fetch recipe suggestion.');
|
|
setError(errorMessage);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}, [ingredients]);
|
|
|
|
return (
|
|
<div className="bg-white dark:bg-gray-800 shadow rounded-lg p-6">
|
|
<h2 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">Get a Recipe Suggestion</h2>
|
|
<p className="text-gray-600 dark:text-gray-400 mb-4">Enter some ingredients you have, separated by commas.</p>
|
|
<form onSubmit={handleSubmit}>
|
|
<div className="mb-4">
|
|
<label htmlFor="ingredients-input" className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Ingredients:</label>
|
|
<input
|
|
id="ingredients-input"
|
|
type="text"
|
|
value={ingredients}
|
|
onChange={(e) => setIngredients(e.target.value)}
|
|
placeholder="e.g., chicken, rice, broccoli"
|
|
disabled={isLoading}
|
|
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600 dark:text-white sm:text-sm p-2 border"
|
|
/>
|
|
</div>
|
|
<button type="submit" disabled={isLoading} className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 transition-colors">
|
|
{isLoading ? 'Getting suggestion...' : 'Suggest a Recipe'}
|
|
</button>
|
|
</form>
|
|
|
|
{error && (
|
|
<div className="mt-4 p-4 bg-red-50 dark:bg-red-900/50 text-red-700 dark:text-red-200 rounded-md text-sm">{error}</div>
|
|
)}
|
|
|
|
{suggestion && (
|
|
<div className="mt-6 bg-gray-50 dark:bg-gray-700/50 rounded-lg p-4 border border-gray-200 dark:border-gray-600">
|
|
<div className="prose dark:prose-invert max-w-none">
|
|
<h5 className="text-lg font-medium text-gray-900 dark:text-white mb-2">Recipe Suggestion</h5>
|
|
<p className="text-gray-700 dark:text-gray-300 whitespace-pre-wrap">{suggestion}</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}; |