FeedForm.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  1. import { useState } from 'react';
  2. import { useForm } from 'react-hook-form';
  3. import { Button } from '@/components/ui/button';
  4. import { Input } from '@/components/ui/input';
  5. import { Textarea } from '@/components/ui/textarea';
  6. import {
  7. Form,
  8. FormControl,
  9. FormField,
  10. FormItem,
  11. FormLabel,
  12. FormMessage,
  13. } from '@/components/ui/form';
  14. import { DialogFooter } from '@/components/ui/dialog';
  15. import { Alert, AlertDescription } from '@/components/ui/alert';
  16. import {
  17. convertYouTubeToRSS,
  18. fetchYouTubeChannelName,
  19. fetchYouTubeRSSUrl,
  20. needsChannelIdLookup,
  21. getChannelIdInstructions,
  22. isDirectRSSFeed
  23. } from '@/utils/youtube';
  24. import { fetchWebsiteRSS, isDirectRSSFeed as isDirectRSSUrl } from '@/utils/rss';
  25. import { feedTypeOptions } from './FeedTypeOptions';
  26. import { NewsCategory } from '@/types/news';
  27. import { AlertTriangle, Info } from 'lucide-react';
  28. interface FeedFormData {
  29. name: string;
  30. url: string;
  31. category: string;
  32. description?: string;
  33. }
  34. interface FeedFormProps {
  35. selectedType: string;
  36. onSubmit: (feedData: any) => void;
  37. onCancel: () => void;
  38. categories: NewsCategory[];
  39. }
  40. const FeedForm = ({ selectedType, onSubmit, onCancel, categories }: FeedFormProps) => {
  41. const [isLoadingChannelName, setIsLoadingChannelName] = useState(false);
  42. const [urlWarning, setUrlWarning] = useState<string | null>(null);
  43. const [showInstructions, setShowInstructions] = useState(false);
  44. const form = useForm<FeedFormData>({
  45. defaultValues: {
  46. name: '',
  47. url: '',
  48. category: '',
  49. description: '',
  50. },
  51. });
  52. const handleSubmit = (data: FeedFormData) => {
  53. let processedUrl = data.url;
  54. // If it's a YouTube feed, convert the URL to RSS format
  55. if (selectedType === 'youtube') {
  56. processedUrl = convertYouTubeToRSS(data.url);
  57. console.log('YouTube URL converted:', { original: data.url, converted: processedUrl });
  58. }
  59. const feedData = {
  60. ...data,
  61. url: processedUrl,
  62. type: selectedType,
  63. id: Date.now().toString(), // Simple ID generation
  64. };
  65. onSubmit(feedData);
  66. };
  67. const handleUrlChange = async (url: string) => {
  68. form.setValue('url', url);
  69. setUrlWarning(null);
  70. setShowInstructions(false);
  71. // If it's a YouTube URL, try to automatically fetch RSS URL and channel name
  72. if (selectedType === 'youtube' && url && url.includes('youtube.com')) {
  73. setIsLoadingChannelName(true);
  74. try {
  75. // Try to automatically fetch the RSS URL and channel name
  76. const result = await fetchYouTubeRSSUrl(url);
  77. if (result) {
  78. // Update the URL field with the correct RSS URL
  79. form.setValue('url', result.rssUrl);
  80. // Set the channel name if we don't have one yet
  81. if (!form.getValues('name') && result.channelName) {
  82. form.setValue('name', result.channelName);
  83. }
  84. setUrlWarning(null);
  85. setShowInstructions(false);
  86. } else {
  87. // Fallback to manual instructions if automatic detection fails
  88. setUrlWarning('Impossible de détecter automatiquement le flux RSS. Veuillez utiliser les instructions ci-dessous.');
  89. setShowInstructions(true);
  90. }
  91. } catch (error) {
  92. console.error('Error fetching YouTube RSS:', error);
  93. setUrlWarning('Erreur lors de la détection automatique. Veuillez utiliser les instructions ci-dessous.');
  94. setShowInstructions(true);
  95. }
  96. setIsLoadingChannelName(false);
  97. }
  98. // If it's an RSS auto URL, try to automatically detect RSS feed
  99. if (selectedType === 'rss-auto' && url && !isDirectRSSUrl(url)) {
  100. setIsLoadingChannelName(true);
  101. try {
  102. const result = await fetchWebsiteRSS(url);
  103. if (result) {
  104. // Update the URL field with the detected RSS URL
  105. form.setValue('url', result.rssUrl);
  106. // Set the site name if we don't have one yet
  107. if (!form.getValues('name') && result.siteName) {
  108. form.setValue('name', result.siteName);
  109. }
  110. setUrlWarning(null);
  111. // If multiple feeds found, show info
  112. if (result.feeds && result.feeds.length > 1) {
  113. setUrlWarning(`✓ ${result.feeds.length} flux RSS détectés. Le premier a été sélectionné.`);
  114. }
  115. } else {
  116. setUrlWarning('Aucun flux RSS détecté automatiquement sur ce site.');
  117. }
  118. } catch (error) {
  119. console.error('Error fetching website RSS:', error);
  120. setUrlWarning('Erreur lors de la détection automatique du flux RSS.');
  121. }
  122. setIsLoadingChannelName(false);
  123. }
  124. };
  125. const selectedTypeOption = feedTypeOptions.find(option => option.value === selectedType);
  126. const getUrlPlaceholder = () => {
  127. switch (selectedType) {
  128. case 'youtube':
  129. return 'https://www.youtube.com/channel/UCxxxxx ou https://www.youtube.com/feeds/videos.xml?channel_id=UCxxxxx';
  130. case 'rss-auto':
  131. return 'https://example.com (le flux RSS sera détecté automatiquement)';
  132. case 'rss-manual':
  133. return 'https://example.com/feed.xml';
  134. default:
  135. return 'https://...';
  136. }
  137. };
  138. const getUrlHelperText = () => {
  139. if (selectedType === 'youtube') {
  140. return 'Utilisez de préférence l\'URL avec l\'ID de chaîne (UC...) ou l\'URL RSS directe pour éviter les erreurs';
  141. }
  142. if (selectedType === 'rss-auto') {
  143. return 'Entrez l\'URL du site web et le flux RSS sera automatiquement détecté';
  144. }
  145. return null;
  146. };
  147. return (
  148. <Form {...form}>
  149. <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
  150. <div className="flex items-center gap-2 p-3 bg-muted rounded-lg">
  151. {selectedTypeOption && (
  152. <>
  153. <div className={`p-2 rounded ${selectedTypeOption.color} text-white`}>
  154. <selectedTypeOption.icon className="h-4 w-4" />
  155. </div>
  156. <span className="font-medium">{selectedTypeOption.label}</span>
  157. </>
  158. )}
  159. </div>
  160. <FormField
  161. control={form.control}
  162. name="name"
  163. render={({ field }) => (
  164. <FormItem>
  165. <FormLabel>
  166. Nom du flux
  167. {isLoadingChannelName && (
  168. <span className="text-xs text-muted-foreground ml-2">
  169. (détection automatique...)
  170. </span>
  171. )}
  172. </FormLabel>
  173. <FormControl>
  174. <Input
  175. placeholder="Nom du flux..."
  176. {...field}
  177. required
  178. disabled={isLoadingChannelName}
  179. />
  180. </FormControl>
  181. <FormMessage />
  182. </FormItem>
  183. )}
  184. />
  185. <FormField
  186. control={form.control}
  187. name="url"
  188. render={({ field }) => (
  189. <FormItem>
  190. <FormLabel>URL</FormLabel>
  191. <FormControl>
  192. <Input
  193. placeholder={getUrlPlaceholder()}
  194. type="url"
  195. {...field}
  196. onChange={(e) => handleUrlChange(e.target.value)}
  197. required
  198. />
  199. </FormControl>
  200. {getUrlHelperText() && (
  201. <p className="text-xs text-muted-foreground mt-1">
  202. {getUrlHelperText()}
  203. </p>
  204. )}
  205. {urlWarning && (
  206. <Alert className="mt-2">
  207. <AlertTriangle className="h-4 w-4" />
  208. <AlertDescription className="text-sm">
  209. {urlWarning}
  210. </AlertDescription>
  211. </Alert>
  212. )}
  213. {showInstructions && selectedType === 'youtube' && (
  214. <Alert className="mt-2">
  215. <Info className="h-4 w-4" />
  216. <AlertDescription className="text-sm whitespace-pre-line">
  217. {getChannelIdInstructions()}
  218. </AlertDescription>
  219. </Alert>
  220. )}
  221. <FormMessage />
  222. </FormItem>
  223. )}
  224. />
  225. <FormField
  226. control={form.control}
  227. name="description"
  228. render={({ field }) => (
  229. <FormItem>
  230. <FormLabel>Description (optionnel)</FormLabel>
  231. <FormControl>
  232. <Textarea
  233. placeholder="Description du flux..."
  234. rows={3}
  235. {...field}
  236. />
  237. </FormControl>
  238. <FormMessage />
  239. </FormItem>
  240. )}
  241. />
  242. <DialogFooter className="gap-2">
  243. <Button type="button" variant="outline" onClick={onCancel}>
  244. Annuler
  245. </Button>
  246. <Button type="submit" disabled={isLoadingChannelName}>
  247. Ajouter le flux
  248. </Button>
  249. </DialogFooter>
  250. </form>
  251. </Form>
  252. );
  253. };
  254. export default FeedForm;