152 lines
4.6 KiB
TypeScript
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>
|
|
);
|
|
}; |