From 74198ebd46d741e2427d1d7d6de3c1bde57b6435 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:26:53 +0000 Subject: [PATCH 1/3] init: placeholder for React version of FavoriteButtonComponent Co-Authored-By: Lukas Burger --- src/app/features/article/components/FavoriteButton.tsx | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 src/app/features/article/components/FavoriteButton.tsx diff --git a/src/app/features/article/components/FavoriteButton.tsx b/src/app/features/article/components/FavoriteButton.tsx new file mode 100644 index 000000000..c44edd781 --- /dev/null +++ b/src/app/features/article/components/FavoriteButton.tsx @@ -0,0 +1,3 @@ +export const FavoriteButton = () => { + return null; +}; From 0b7c2814f5d0feccce69241087e4661689b29005 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:28:46 +0000 Subject: [PATCH 2/3] feat: port logic from Angular to React feat: translate template to JSX feat: wire up inputs, events, and dependencies Co-Authored-By: Lukas Burger --- .../article/components/FavoriteButton.tsx | 98 ++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/src/app/features/article/components/FavoriteButton.tsx b/src/app/features/article/components/FavoriteButton.tsx index c44edd781..f87dab7de 100644 --- a/src/app/features/article/components/FavoriteButton.tsx +++ b/src/app/features/article/components/FavoriteButton.tsx @@ -1,3 +1,97 @@ -export const FavoriteButton = () => { - return null; +import React, { useState, useCallback } from 'react'; + +interface Article { + slug: string; + title: string; + description: string; + body: string; + tagList: string[]; + createdAt: string; + updatedAt: string; + favorited: boolean; + favoritesCount: number; + author: { + username: string; + bio: string; + image: string; + following: boolean; + }; +} + +interface FavoriteButtonProps { + article: Article; + children?: React.ReactNode; + /** Called after a successful toggle. Receives the new `favorited` state. */ + onToggle?: (favorited: boolean) => void; + /** Whether the current user is authenticated. */ + isAuthenticated?: boolean; + /** Callback to navigate when user is not authenticated. */ + onNavigateToRegister?: () => void; + /** + * Service function to favorite an article by slug. + * TODO: Wire up to actual ArticlesService when integrating. + */ + onFavorite?: (slug: string) => Promise; + /** + * Service function to unfavorite an article by slug. + * TODO: Wire up to actual ArticlesService when integrating. + */ + onUnfavorite?: (slug: string) => Promise; +} + +/** + * React equivalent of the Angular `FavoriteButtonComponent`. + * + * Renders a favorite/unfavorite button for an article. + * Uses `` equivalent via `props.children`. + * Mirrors the Angular signal-based `isSubmitting` state with `useState`. + */ +export const FavoriteButton: React.FC = ({ + article, + children, + onToggle, + isAuthenticated = false, + onNavigateToRegister, + onFavorite, + onUnfavorite, +}) => { + const [isSubmitting, setIsSubmitting] = useState(false); + + const toggleFavorite = useCallback(async () => { + setIsSubmitting(true); + + if (!isAuthenticated) { + onNavigateToRegister?.(); + setIsSubmitting(false); + return; + } + + try { + if (!article.favorited) { + await onFavorite?.(article.slug); + } else { + await onUnfavorite?.(article.slug); + } + onToggle?.(!article.favorited); + } catch { + // Error handling — matches Angular's error callback (no-op) + } finally { + setIsSubmitting(false); + } + }, [article, isAuthenticated, onFavorite, onUnfavorite, onToggle, onNavigateToRegister]); + + const buttonClass = [ + 'btn', + 'btn-sm', + isSubmitting ? 'disabled' : '', + article.favorited ? 'btn-primary' : 'btn-outline-primary', + ] + .filter(Boolean) + .join(' '); + + return ( + + ); }; From b3d2c79520f9ec2da9def3a0b5e9c57b311716a8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:35:32 +0000 Subject: [PATCH 3/3] fix: guard onToggle to only fire after successful API call Co-Authored-By: Lukas Burger --- .../features/article/components/FavoriteButton.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/features/article/components/FavoriteButton.tsx b/src/app/features/article/components/FavoriteButton.tsx index f87dab7de..a1d895b26 100644 --- a/src/app/features/article/components/FavoriteButton.tsx +++ b/src/app/features/article/components/FavoriteButton.tsx @@ -68,9 +68,17 @@ export const FavoriteButton: React.FC = ({ try { if (!article.favorited) { - await onFavorite?.(article.slug); + if (onFavorite) { + await onFavorite(article.slug); + } else { + return; + } } else { - await onUnfavorite?.(article.slug); + if (onUnfavorite) { + await onUnfavorite(article.slug); + } else { + return; + } } onToggle?.(!article.favorited); } catch {