Dateien nach "b2b-marketing-assistant/components" hochladen
This commit is contained in:
135
b2b-marketing-assistant/components/InputForm.tsx
Normal file
135
b2b-marketing-assistant/components/InputForm.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
|
||||
import React from 'react';
|
||||
import type { InputData } from '../types';
|
||||
import { LANGUAGE_OPTIONS, CHANNEL_OPTIONS, translations } from '../constants';
|
||||
import { LoadingSpinner, SparklesIcon } from './Icons';
|
||||
|
||||
interface InputFormProps {
|
||||
inputData: InputData;
|
||||
setInputData: React.Dispatch<React.SetStateAction<InputData>>;
|
||||
onGenerate: () => void;
|
||||
isLoading: boolean;
|
||||
t: typeof translations.de;
|
||||
}
|
||||
|
||||
export const InputForm: React.FC<InputFormProps> = ({ inputData, setInputData, onGenerate, isLoading, t }) => {
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setInputData(prev => ({ ...prev, [name]: value }));
|
||||
};
|
||||
|
||||
const handleChannelChange = (channel: string) => {
|
||||
setInputData(prev => {
|
||||
const newChannels = prev.channels.includes(channel)
|
||||
? prev.channels.filter(c => c !== channel)
|
||||
: [...prev.channels, channel];
|
||||
return { ...prev, channels: newChannels };
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={(e) => { e.preventDefault(); onGenerate(); }} className="space-y-6">
|
||||
<div>
|
||||
<label htmlFor="companyUrl" className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
{t.companyUrlLabel}
|
||||
</label>
|
||||
<input
|
||||
type="url"
|
||||
name="companyUrl"
|
||||
id="companyUrl"
|
||||
value={inputData.companyUrl}
|
||||
onChange={handleInputChange}
|
||||
className="block w-full px-4 py-2 bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-600 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500"
|
||||
placeholder={t.companyUrlPlaceholder}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label htmlFor="language" className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
{t.targetLanguageLabel}
|
||||
</label>
|
||||
<select
|
||||
id="language"
|
||||
name="language"
|
||||
value={inputData.language}
|
||||
onChange={handleInputChange}
|
||||
className="block w-full pl-3 pr-10 py-2 bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-600 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500"
|
||||
>
|
||||
{LANGUAGE_OPTIONS.map(option => (
|
||||
<option key={option.value} value={option.value}>{option.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="focus" className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
{t.productFocusLabel} <span className="text-slate-400">{t.productFocusOptional}</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="focus"
|
||||
id="focus"
|
||||
value={inputData.focus}
|
||||
onChange={handleInputChange}
|
||||
className="block w-full px-4 py-2 bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-600 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500"
|
||||
placeholder={t.productFocusPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="regions" className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-1">
|
||||
{t.regionsLabel} <span className="text-slate-400">{t.regionsOptional}</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="regions"
|
||||
id="regions"
|
||||
value={inputData.regions}
|
||||
onChange={handleInputChange}
|
||||
className="block w-full px-4 py-2 bg-white dark:bg-slate-900 border border-slate-300 dark:border-slate-600 rounded-md shadow-sm placeholder-slate-400 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:border-sky-500"
|
||||
placeholder={t.regionsPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||
{t.channelsLabel} <span className="text-slate-400">{t.channelsOptional}</span>
|
||||
</label>
|
||||
<div className="flex flex-wrap gap-4">
|
||||
{CHANNEL_OPTIONS.map(channel => (
|
||||
<label key={channel} className="flex items-center space-x-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={inputData.channels.includes(channel)}
|
||||
onChange={() => handleChannelChange(channel)}
|
||||
className="h-4 w-4 rounded border-slate-300 dark:border-slate-600 text-sky-600 focus:ring-sky-500 bg-slate-100 dark:bg-slate-700"
|
||||
/>
|
||||
<span className="text-sm text-slate-600 dark:text-slate-300">{channel}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="pt-2">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className="w-full flex items-center justify-center px-6 py-3 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-sky-600 hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500 disabled:bg-slate-400 dark:disabled:bg-slate-600 disabled:cursor-not-allowed transition-colors duration-200"
|
||||
>
|
||||
{isLoading ? (
|
||||
<>
|
||||
<LoadingSpinner className="mr-3" />
|
||||
{t.analyzingButton}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<SparklesIcon className="mr-2 h-5 w-5" />
|
||||
{t.generateButton}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user