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..9de25ac --- /dev/null +++ b/website/src/app/contacts/contact-detail/ContactDetailComponent.tsx @@ -0,0 +1,177 @@ +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} +
+
+
+ ); + } + + return ( + <> + {/* Contact card or not-found message */} + {contact !== null ? ( +
+
+
+ 😊 +
+

{contact.name}

+

{contact.email}

+

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

+
+
+
+
+ ) : ( +
+
+ {NO_CONTACT_FOUND_MESSAGE} +
+
+ )} + + {/* Buttons always visible when not loading (matches Angular *ngIf="!isLoading") */} +
+
+ +
+
+ +
+
+ + ); +};