youtube-summarizer/frontend/src/components/forms/SummarizeForm.tsx

152 lines
4.6 KiB
TypeScript

import React, { useState, useCallback, useEffect } from 'react';
import { useURLValidation } from '../../hooks/useURLValidation';
import { ValidationFeedback, ValidationIcon } from '../ui/ValidationFeedback';
interface SummarizeFormProps {
onSubmit?: (videoId: string, videoUrl: string) => void;
className?: string;
}
// Debounce helper
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
export const SummarizeForm: React.FC<SummarizeFormProps> = ({
onSubmit,
className = '',
}) => {
const [url, setUrl] = useState('');
const [hasInteracted, setHasInteracted] = useState(false);
const { validateURL, validateURLClient, resetValidation, validationState } = useURLValidation();
// Debounce the URL for validation
const debouncedUrl = useDebounce(url, 500);
// Validate on debounced URL change
useEffect(() => {
if (debouncedUrl && hasInteracted) {
// First do quick client-side validation
const clientResult = validateURLClient(debouncedUrl);
if (clientResult.isValid) {
// If client validation passes, do server validation
validateURL(debouncedUrl);
}
} else if (!debouncedUrl && hasInteracted) {
resetValidation();
}
}, [debouncedUrl, hasInteracted, validateURL, validateURLClient, resetValidation]);
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const newUrl = e.target.value;
setUrl(newUrl);
if (!hasInteracted) {
setHasInteracted(true);
}
// Immediate client-side validation for instant feedback
if (newUrl) {
validateURLClient(newUrl);
} else {
resetValidation();
}
}, [hasInteracted, validateURLClient, resetValidation]);
const handleSubmit = useCallback(async (e: React.FormEvent) => {
e.preventDefault();
if (!url) {
setHasInteracted(true);
return;
}
// Validate before submission
const result = await validateURL(url);
if (result.isValid && result.videoId && result.videoUrl && onSubmit) {
onSubmit(result.videoId, result.videoUrl);
}
}, [url, validateURL, onSubmit]);
const handlePaste = useCallback((e: React.ClipboardEvent<HTMLInputElement>) => {
// Immediately validate pasted content
setTimeout(() => {
const pastedUrl = e.currentTarget.value;
if (pastedUrl) {
setHasInteracted(true);
validateURL(pastedUrl);
}
}, 0);
}, [validateURL]);
const isSubmitDisabled = !url || validationState.isValidating || !validationState.isValid;
return (
<form onSubmit={handleSubmit} className={`summarize-form ${className}`}>
<div className="form-group">
<label htmlFor="youtube-url">YouTube URL</label>
<div className="input-wrapper">
<input
id="youtube-url"
type="text"
value={url}
onChange={handleInputChange}
onPaste={handlePaste}
placeholder="Paste YouTube URL here (e.g., https://youtube.com/watch?v=...)"
className={`url-input ${
hasInteracted && validationState.error ? 'error' : ''
} ${
hasInteracted && validationState.isValid ? 'success' : ''
}`}
aria-invalid={hasInteracted && !!validationState.error}
aria-describedby="url-validation-feedback"
/>
<ValidationIcon state={validationState} />
</div>
{hasInteracted && (
<ValidationFeedback
validationState={validationState}
className="mt-2"
/>
)}
</div>
<button
type="submit"
disabled={isSubmitDisabled}
className={`submit-button ${isSubmitDisabled ? 'disabled' : 'enabled'}`}
>
{validationState.isValidating ? 'Validating...' : 'Summarize Video'}
</button>
<div className="form-help">
<details>
<summary>Supported URL formats</summary>
<ul>
<li><code>https://youtube.com/watch?v=VIDEO_ID</code></li>
<li><code>https://youtu.be/VIDEO_ID</code></li>
<li><code>https://youtube.com/embed/VIDEO_ID</code></li>
<li><code>https://m.youtube.com/watch?v=VIDEO_ID</code></li>
</ul>
<p className="note">
Note: Playlist URLs are not currently supported. Please provide individual video URLs.
</p>
</details>
</div>
</form>
);
};