From 9c9ef3397fbe558b190364ff574e458431072774 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:10:18 +0000 Subject: [PATCH 1/2] init: placeholder for React version of ContactDetailComponent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat: port logic from Angular to React feat: translate template to JSX feat: wire up inputs, events, and dependencies Migrates ContactDetailComponent from Angular to React: - ContactService.getContact → getContact prop - ActivatedRoute params → contactId prop - MatDialog.open → onOpenFeedDialog callback prop - ngOnInit loadContact → useEffect with cleanup - *ngIf loading/contact/null states → conditional JSX returns - mat-card → div card with inline styles - phoneNumber pipe → formatPhoneNumber utility (TODO) - routerLink → onNavigateBack callback - setTimeout for dialog preserved as-is Co-Authored-By: Lukas Burger --- .../contact-detail/ContactDetailComponent.tsx | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 website/src/app/contacts/contact-detail/ContactDetailComponent.tsx diff --git a/website/src/app/contacts/contact-detail/ContactDetailComponent.tsx b/website/src/app/contacts/contact-detail/ContactDetailComponent.tsx new file mode 100644 index 0000000..9e1ef48 --- /dev/null +++ b/website/src/app/contacts/contact-detail/ContactDetailComponent.tsx @@ -0,0 +1,179 @@ +import React, { useState, useEffect } from 'react'; + +/** + * React port of Angular ContactDetailComponent + * + * Original Angular component: contact-detail.component.ts + * - @Input: none (route param `id` used) + * - @Output: none + * - Services: ContactService, ActivatedRoute, MatDialog + * - Lifecycle: ngOnInit → useEffect + */ + +interface Contact { + id: number; + name?: string; + email?: string; + number?: string; + country?: string; + favorite?: boolean; +} + +const LOADING_CONTACT_MESSAGE = 'Loading contact...'; +const NO_CONTACT_FOUND_MESSAGE = 'Contact not found'; + +/** TODO: Replace with a proper phone formatting library or utility */ +function formatPhoneNumber(phone?: string, _format?: string, _country?: string): string { + return phone ?? ''; +} + +interface ContactDetailComponentProps { + /** The contact ID to fetch, typically from route params */ + contactId: number; + /** Function to fetch contact data */ + getContact: (id: number) => Promise; + /** Callback to navigate back to contacts list */ + onNavigateBack: () => void; + /** Callback to open the contact feed dialog */ + onOpenFeedDialog?: (contactName: string) => void; +} + +export const ContactDetailComponent: React.FC = ({ + contactId, + getContact, + onNavigateBack, + onOpenFeedDialog, +}) => { + const [isLoading, setIsLoading] = useState(true); + const [contact, setContact] = useState(null); + + // Replaces ngOnInit + loadContact() + useEffect(() => { + let cancelled = false; + + setIsLoading(true); + setContact(null); + + getContact(contactId) + .then((data) => { + if (!cancelled) { + setIsLoading(false); + setContact(data); + } + }) + .catch(() => { + if (!cancelled) { + setIsLoading(false); + setContact(null); + } + }); + + // Cleanup on unmount (replaces ngOnDestroy) + return () => { + cancelled = true; + }; + }, [contactId, getContact]); + + // Replaces openDialog() + const handleOpenDialog = () => { + if (contact?.name && onOpenFeedDialog) { + setTimeout(() => { + onOpenFeedDialog(contact.name!); + }, 500); + } + }; + + // Loading state + if (isLoading) { + return ( +
+
+ {LOADING_CONTACT_MESSAGE} +
+
+
+ ); + } + + // Contact loaded + if (contact !== null) { + return ( + <> +
+
+
+ 😊 +
+

{contact.name}

+

{contact.email}

+

+ {formatPhoneNumber(contact.number, 'default', contact.country)} +

+
+
+
+
+ +
+
+ +
+
+ +
+
+ + ); + } + + // No contact found + return ( +
+
+ {NO_CONTACT_FOUND_MESSAGE} +
+
+ ); +}; From 0ec801a70557856303b2e5505d098c85a87e45e0 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 13 Apr 2026 19:17:24 +0000 Subject: [PATCH 2/2] fix: show navigation buttons in contact-not-found state Buttons (back and feed) are now rendered whenever loading is complete, matching the Angular original's *ngIf='!isLoading' behavior. Previously they were only visible when a contact was found. Co-Authored-By: Lukas Burger --- .../contact-detail/ContactDetailComponent.tsx | 110 +++++++++--------- 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/website/src/app/contacts/contact-detail/ContactDetailComponent.tsx b/website/src/app/contacts/contact-detail/ContactDetailComponent.tsx index 9e1ef48..9de25ac 100644 --- a/website/src/app/contacts/contact-detail/ContactDetailComponent.tsx +++ b/website/src/app/contacts/contact-detail/ContactDetailComponent.tsx @@ -103,10 +103,10 @@ export const ContactDetailComponent: React.FC = ({ ); } - // Contact loaded - if (contact !== null) { - return ( - <> + return ( + <> + {/* Contact card or not-found message */} + {contact !== null ? (
@@ -121,59 +121,57 @@ export const ContactDetailComponent: React.FC = ({
- -
-
- -
-
- -
+ ) : ( +
+
+ {NO_CONTACT_FOUND_MESSAGE} +
- - ); - } + )} - // No contact found - return ( -
-
- {NO_CONTACT_FOUND_MESSAGE} -
-
+ {/* Buttons always visible when not loading (matches Angular *ngIf="!isLoading") */} +
+
+ +
+
+ +
+
+ ); };