@trii/components 2.0.25 → 2.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1,2 @@
1
1
  export { default as ContactInfoPopup } from './ContactInfoPopup';
2
+ export { default as EditContactModal } from './EditContactModal';
package/dist/esm/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
- import React__default, { useCallback, useEffect } from 'react';
3
- import { styled as styled$3, Box, IconButton, Avatar, Typography, CircularProgress, Tooltip, Chip, Popper, ClickAwayListener, CardContent } from '@mui/material';
2
+ import React__default, { useCallback, useEffect, useRef, useState, useMemo } from 'react';
3
+ import { styled as styled$3, Box, IconButton, Avatar, Typography, CircularProgress, Tooltip, Chip, Popper, ClickAwayListener, CardContent, Modal, Backdrop, Fade } from '@mui/material';
4
4
  import { withEmotionCache, ThemeContext, CacheProvider, Global, css, keyframes } from '@emotion/react';
5
5
 
6
6
  var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
@@ -8641,19 +8641,19 @@ const ButtonsContainer = styled$3(Box)(({ theme }) => ({
8641
8641
  const SmallIconButton = styled$3(IconButton)({
8642
8642
  padding: 4,
8643
8643
  });
8644
- const HeaderContainer = styled$3(Box)({
8644
+ const HeaderContainer$1 = styled$3(Box)({
8645
8645
  display: 'flex',
8646
8646
  flexDirection: 'column',
8647
8647
  alignItems: 'center',
8648
8648
  marginBottom: '16px',
8649
8649
  position: 'relative',
8650
8650
  });
8651
- const ContactAvatar = styled$3(Avatar)({
8651
+ const ContactAvatar$1 = styled$3(Avatar)({
8652
8652
  width: 60,
8653
8653
  height: 60,
8654
8654
  marginBottom: 8,
8655
8655
  });
8656
- const ContactName = styled$3(Typography)({
8656
+ const ContactName$1 = styled$3(Typography)({
8657
8657
  whiteSpace: 'nowrap',
8658
8658
  overflow: 'hidden',
8659
8659
  textOverflow: 'ellipsis',
@@ -8668,7 +8668,7 @@ const LoadingContainer = styled$3(Box)({
8668
8668
  height: 60,
8669
8669
  marginBottom: 8,
8670
8670
  });
8671
- const Header = ({ imgUrl, name, contactId, navigate, isLoading = false, onError, onClose, isBusiness = false, }) => {
8671
+ const Header$1 = ({ imgUrl, name, contactId, navigate, isLoading = false, onError, onClose, isBusiness = false, }) => {
8672
8672
  const handleNavigation = useCallback((url) => (event) => {
8673
8673
  try {
8674
8674
  event.preventDefault();
@@ -8694,7 +8694,7 @@ const Header = ({ imgUrl, name, contactId, navigate, isLoading = false, onError,
8694
8694
  const handleNavigateToConversations = handleNavigation('/a/conversations/conversations');
8695
8695
  const displayName = name || 'Unknown Contact';
8696
8696
  const avatarAlt = `Avatar for ${displayName}`;
8697
- return (jsxRuntimeExports.jsxs(HeaderContainer, { children: [jsxRuntimeExports.jsxs(ButtonsContainer, { children: [jsxRuntimeExports.jsx(SmallIconButton, { color: "info", size: "small", onClick: (e) => {
8697
+ return (jsxRuntimeExports.jsxs(HeaderContainer$1, { children: [jsxRuntimeExports.jsxs(ButtonsContainer, { children: [jsxRuntimeExports.jsx(SmallIconButton, { color: "info", size: "small", onClick: (e) => {
8698
8698
  handleNavigateToContacts(e);
8699
8699
  onClose();
8700
8700
  }, onMouseDown: (e) => {
@@ -8712,7 +8712,7 @@ const Header = ({ imgUrl, name, contactId, navigate, isLoading = false, onError,
8712
8712
  handleNavigateToConversations(e);
8713
8713
  onClose();
8714
8714
  }
8715
- }, "aria-label": "Go to conversations", children: jsxRuntimeExports.jsx(default_1, { fontSize: "small" }) })] }), isLoading ? (jsxRuntimeExports.jsx(LoadingContainer, { children: jsxRuntimeExports.jsx(CircularProgress, { size: 24 }) })) : (jsxRuntimeExports.jsx(ContactAvatar, { src: imgUrl, alt: avatarAlt, onError: () => onError?.(new Error('Failed to load avatar')) })), jsxRuntimeExports.jsx(ContactName, { variant: "h6", title: displayName, children: displayName })] }));
8715
+ }, "aria-label": "Go to conversations", children: jsxRuntimeExports.jsx(default_1, { fontSize: "small" }) })] }), isLoading ? (jsxRuntimeExports.jsx(LoadingContainer, { children: jsxRuntimeExports.jsx(CircularProgress, { size: 24 }) })) : (jsxRuntimeExports.jsx(ContactAvatar$1, { src: imgUrl, alt: avatarAlt, onError: () => onError?.(new Error('Failed to load avatar')) })), jsxRuntimeExports.jsx(ContactName$1, { variant: "h6", title: displayName, children: displayName })] }));
8716
8716
  };
8717
8717
 
8718
8718
  const Container$1 = styled$3(Box)({
@@ -14651,7 +14651,7 @@ const LabelsSection = ({ contactData, title }) => {
14651
14651
  * @param obj The object to check
14652
14652
  * @returns True if the object is an IContact
14653
14653
  */
14654
- function isContact(obj) {
14654
+ function isContact$1(obj) {
14655
14655
  // Si está marcado explícitamente como negocio, no es un contacto regular
14656
14656
  if (obj?.isBusiness === true) {
14657
14657
  return false;
@@ -14677,7 +14677,7 @@ const SectionTitle$1 = styled$3(Typography)({
14677
14677
  borderBottom: `1px solid lightgray`,
14678
14678
  });
14679
14679
  const BusinessSection = ({ contactData, title }) => {
14680
- if (!contactData || !isContact(contactData))
14680
+ if (!contactData || !isContact$1(contactData))
14681
14681
  return null;
14682
14682
  return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx(SectionTitle$1, { gutterBottom: true, mt: 2, variant: "subtitle1", children: title }), jsxRuntimeExports.jsx(Typography, { variant: "body2", color: "text.secondary", children: contactData?.businessName })] }));
14683
14683
  };
@@ -14687,7 +14687,7 @@ const BusinessSection = ({ contactData, title }) => {
14687
14687
  * @param obj The object to check
14688
14688
  * @returns True if the object is an IBusiness
14689
14689
  */
14690
- function isBusiness(obj) {
14690
+ function isBusiness$1(obj) {
14691
14691
  if (obj?.isBusiness === true) {
14692
14692
  // Si se ha marcado explícitamente como negocio con la propiedad isBusiness
14693
14693
  return true;
@@ -14737,7 +14737,7 @@ const SectionTitle = styled$3(Typography)({
14737
14737
  borderBottom: `1px solid lightgray`,
14738
14738
  });
14739
14739
  const MembersSection = ({ contactData, title, navigate }) => {
14740
- if (!contactData || !isBusiness(contactData) || !contactData.members?.length)
14740
+ if (!contactData || !isBusiness$1(contactData) || !contactData.members?.length)
14741
14741
  return null;
14742
14742
  return (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx(SectionTitle, { gutterBottom: true, mt: 2, variant: "subtitle1", children: title }), contactData?.members.map((member) => (jsxRuntimeExports.jsx(MemberItem, { memberId: String(member.id), name: member.name, navigate: navigate }, member.id)))] }));
14743
14743
  };
@@ -14778,8 +14778,8 @@ const ContactInfoPopup = ({ open, anchorEl, onClose, contactData, avatarImgUrl,
14778
14778
  view: 'Ver',
14779
14779
  email: 'Correo Electrónico',
14780
14780
  }[key] || key), }) => {
14781
- const dataIsBusiness = isBusiness(contactData);
14782
- const dataIsContact = isContact(contactData);
14781
+ const dataIsBusiness = isBusiness$1(contactData);
14782
+ const dataIsContact = isContact$1(contactData);
14783
14783
  const contactMethods = [
14784
14784
  {
14785
14785
  icon: jsxRuntimeExports.jsx(PhoneEnabled, { fontSize: "small" }),
@@ -14828,8 +14828,235 @@ const ContactInfoPopup = ({ open, anchorEl, onClose, contactData, avatarImgUrl,
14828
14828
  window.removeEventListener('keydown', handleKeyDown);
14829
14829
  };
14830
14830
  }, [open, onClose]);
14831
- return (jsxRuntimeExports.jsx(Popper, { sx: { zIndex: 1300 }, open: open, anchorEl: anchorEl, placement: "bottom-start", "data-popper-child": "true", children: jsxRuntimeExports.jsx(ClickAwayListener, { onClickAway: onClose, children: jsxRuntimeExports.jsx(PopupContainer, { children: jsxRuntimeExports.jsxs(CardContent, { children: [jsxRuntimeExports.jsx(Header, { navigate: navigate, contactId: contactData?.id, imgUrl: avatarImgUrl, name: contactData?.name, onClose: onClose, isBusiness: dataIsBusiness }), jsxRuntimeExports.jsx(LabelsSection, { contactData: contactData, title: t('labels') }), dataIsContact && (jsxRuntimeExports.jsx(BusinessSection, { contactData: contactData, title: t('business') })), dataIsBusiness && (jsxRuntimeExports.jsx(MembersSection, { contactData: contactData, title: t('businessMembers'), navigate: navigate })), contactMethods.map((method, index) => (jsxRuntimeExports.jsx(ContactMethod, { icon: method.icon, title: method.title, contactList: method.contactList, showTitle: method.showTitle }, index))), jsxRuntimeExports.jsx(Properties, { properties: contactData?.properties, title: t('properties') })] }) }) }) }));
14831
+ return (jsxRuntimeExports.jsx(Popper, { sx: { zIndex: 1300 }, open: open, anchorEl: anchorEl, placement: "bottom-start", "data-popper-child": "true", children: jsxRuntimeExports.jsx(ClickAwayListener, { onClickAway: onClose, children: jsxRuntimeExports.jsx(PopupContainer, { children: jsxRuntimeExports.jsxs(CardContent, { children: [jsxRuntimeExports.jsx(Header$1, { navigate: navigate, contactId: contactData?.id, imgUrl: avatarImgUrl, name: contactData?.name, onClose: onClose, isBusiness: dataIsBusiness }), jsxRuntimeExports.jsx(LabelsSection, { contactData: contactData, title: t('labels') }), dataIsContact && (jsxRuntimeExports.jsx(BusinessSection, { contactData: contactData, title: t('business') })), dataIsBusiness && (jsxRuntimeExports.jsx(MembersSection, { contactData: contactData, title: t('businessMembers'), navigate: navigate })), contactMethods.map((method, index) => (jsxRuntimeExports.jsx(ContactMethod, { icon: method.icon, title: method.title, contactList: method.contactList, showTitle: method.showTitle }, index))), jsxRuntimeExports.jsx(Properties, { properties: contactData?.properties, title: t('properties') })] }) }) }) }));
14832
+ };
14833
+
14834
+ const HeaderContainer = styled$3(Box)({
14835
+ display: 'flex',
14836
+ flexDirection: 'column',
14837
+ alignItems: 'center',
14838
+ marginBottom: '16px',
14839
+ position: 'relative',
14840
+ });
14841
+ const ContactAvatar = styled$3(Avatar)({
14842
+ width: 60,
14843
+ height: 60,
14844
+ marginBottom: 8,
14845
+ });
14846
+ const ContactName = styled$3(Typography)({
14847
+ whiteSpace: 'nowrap',
14848
+ overflow: 'hidden',
14849
+ textOverflow: 'ellipsis',
14850
+ maxWidth: 200,
14851
+ textAlign: 'center',
14852
+ });
14853
+ const Header = ({ imgUrl, displayName = 'Unknown Contact', onError, }) => {
14854
+ const avatarAlt = `Avatar for ${displayName}`;
14855
+ return (jsxRuntimeExports.jsxs(HeaderContainer, { children: [jsxRuntimeExports.jsx(ContactAvatar, { src: imgUrl, alt: avatarAlt, onError: () => onError?.(new Error('Failed to load avatar')) }), jsxRuntimeExports.jsx(ContactName, { variant: "h6", title: displayName, children: displayName })] }));
14856
+ };
14857
+
14858
+ class ApiError extends Error {
14859
+ status;
14860
+ body;
14861
+ constructor(message, status, body) {
14862
+ super(message);
14863
+ this.name = 'ApiError';
14864
+ this.status = status;
14865
+ this.body = body;
14866
+ }
14867
+ }
14868
+ async function requestJson({ url, method = 'GET', headers, body, signal, }) {
14869
+ const res = await globalThis.fetch(url, {
14870
+ method,
14871
+ headers: {
14872
+ ...(body ? { 'Content-Type': 'application/json' } : {}),
14873
+ ...(headers ?? {}),
14874
+ },
14875
+ body: body ? JSON.stringify(body) : undefined,
14876
+ signal,
14877
+ });
14878
+ const contentType = res.headers.get('content-type') || '';
14879
+ const isJson = contentType.includes('application/json');
14880
+ const parsedBody = isJson ? await res.json().catch(() => null) : await res.text().catch(() => null);
14881
+ if (!res.ok) {
14882
+ const message = typeof parsedBody === 'object' && parsedBody && 'message' in parsedBody
14883
+ ? String(parsedBody.message)
14884
+ : `Request failed with status ${res.status}`;
14885
+ throw new ApiError(message, res.status, parsedBody);
14886
+ }
14887
+ return parsedBody;
14888
+ }
14889
+
14890
+ function joinUrl(baseUrl, path) {
14891
+ const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
14892
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
14893
+ return `${normalizedBase}${normalizedPath}`;
14894
+ }
14895
+ async function fetchContact({ baseUrl, contactId, signal }) {
14896
+ const url = joinUrl(baseUrl, `/contacts/${encodeURIComponent(contactId)}`);
14897
+ return requestJson({ url, signal });
14898
+ }
14899
+
14900
+ /**
14901
+ * Type guard to check if an object is an IBusiness
14902
+ * @param obj The object to check
14903
+ * @returns True if the object is an IBusiness
14904
+ */
14905
+ function isBusiness(obj) {
14906
+ if (obj?.isBusiness === true) {
14907
+ // Si se ha marcado explícitamente como negocio con la propiedad isBusiness
14908
+ return true;
14909
+ }
14910
+ return (obj !== null &&
14911
+ typeof obj === 'object' &&
14912
+ typeof obj.id === 'string' &&
14913
+ typeof obj.spaceId === 'string' &&
14914
+ typeof obj.name === 'string' &&
14915
+ Array.isArray(obj.membersId) &&
14916
+ Array.isArray(obj.members) &&
14917
+ Array.isArray(obj.phones) &&
14918
+ Array.isArray(obj.emails) &&
14919
+ typeof obj.imageUrl === 'string' &&
14920
+ !('firstName' in obj) &&
14921
+ !('lastName' in obj));
14922
+ }
14923
+
14924
+ /**
14925
+ * Type guard to check if an object is an IContact
14926
+ * @param obj The object to check
14927
+ * @returns True if the object is an IContact
14928
+ */
14929
+ function isContact(obj) {
14930
+ // Si está marcado explícitamente como negocio, no es un contacto regular
14931
+ if (obj?.isBusiness === true) {
14932
+ return false;
14933
+ }
14934
+ return (obj !== null &&
14935
+ typeof obj === 'object' &&
14936
+ typeof obj.id === 'string' &&
14937
+ typeof obj.spaceId === 'string' &&
14938
+ typeof obj.name === 'string' &&
14939
+ typeof obj.firstName === 'string' &&
14940
+ typeof obj.lastName === 'string' &&
14941
+ Array.isArray(obj.phones) &&
14942
+ Array.isArray(obj.emails) &&
14943
+ 'origin' in obj &&
14944
+ typeof obj.isSpam === 'boolean' &&
14945
+ (!obj.businessId || typeof obj.businessId === 'string'));
14946
+ }
14947
+
14948
+ function useEditContactModalController({ open, baseUrl, contactId, }) {
14949
+ const abortRef = useRef(null);
14950
+ const [state, setState] = useState({
14951
+ isLoading: false,
14952
+ error: null,
14953
+ contactData: null,
14954
+ });
14955
+ const canFetch = useMemo(() => Boolean(open && baseUrl && contactId), [open, baseUrl, contactId]);
14956
+ useEffect(() => {
14957
+ if (open)
14958
+ return;
14959
+ abortRef.current?.abort();
14960
+ setState({ isLoading: false, error: null, contactData: null });
14961
+ }, [open]);
14962
+ useEffect(() => {
14963
+ if (!canFetch) {
14964
+ return;
14965
+ }
14966
+ abortRef.current?.abort();
14967
+ const controller = new AbortController();
14968
+ abortRef.current = controller;
14969
+ setState({ isLoading: true, error: null, contactData: null });
14970
+ fetchContact({ baseUrl: baseUrl, contactId: contactId, signal: controller.signal })
14971
+ .then((data) => {
14972
+ setState({ isLoading: false, error: null, contactData: data });
14973
+ })
14974
+ .catch((err) => {
14975
+ if (controller.signal.aborted)
14976
+ return;
14977
+ setState((prev) => ({ ...prev, isLoading: false, error: err }));
14978
+ });
14979
+ return () => {
14980
+ controller.abort();
14981
+ };
14982
+ }, [canFetch, baseUrl, contactId]);
14983
+ const selectors = useMemo(() => {
14984
+ const contactData = state.contactData;
14985
+ const business = isBusiness(contactData);
14986
+ const contact = isContact(contactData);
14987
+ let contactType = 'unknown';
14988
+ if (business)
14989
+ contactType = 'business';
14990
+ if (contact)
14991
+ contactType = 'contact';
14992
+ let displayName = 'Unknown Contact';
14993
+ if (business) {
14994
+ displayName = contactData.name;
14995
+ }
14996
+ else if (contact) {
14997
+ displayName = `${contactData.firstName} ${contactData.lastName}`.trim();
14998
+ }
14999
+ return {
15000
+ isBusiness: business,
15001
+ isContact: contact,
15002
+ contactType,
15003
+ displayName,
15004
+ };
15005
+ }, [state.contactData]);
15006
+ return {
15007
+ state,
15008
+ selectors,
15009
+ actions: {
15010
+ setContactData: (contactData) => setState((prev) => ({ ...prev, contactData: contactData ?? null })),
15011
+ clearError: () => setState((prev) => ({ ...prev, error: null })),
15012
+ },
15013
+ };
15014
+ }
15015
+
15016
+ const EditContactModal = ({ open, onClose, baseUrl, contactId, sx,
15017
+ // t
15018
+ }) => {
15019
+ const { state, selectors } = useEditContactModalController({
15020
+ open,
15021
+ baseUrl,
15022
+ contactId,
15023
+ });
15024
+ const handleClose = (event, reason) => {
15025
+ if (reason === 'escapeKeyDown') {
15026
+ const maybeEvent = event;
15027
+ if (typeof maybeEvent?.stopPropagation === 'function') {
15028
+ maybeEvent.stopPropagation();
15029
+ }
15030
+ if (typeof maybeEvent?.preventDefault === 'function') {
15031
+ maybeEvent.preventDefault();
15032
+ }
15033
+ }
15034
+ onClose();
15035
+ };
15036
+ const baseSx = {
15037
+ position: 'absolute',
15038
+ top: '50%',
15039
+ left: '50%',
15040
+ transform: 'translate(-50%, -50%)',
15041
+ width: 'min(600px, calc(100vw - 32px))',
15042
+ bgcolor: 'background.paper',
15043
+ borderRadius: 2,
15044
+ boxShadow: 24,
15045
+ outline: 0,
15046
+ p: 2,
15047
+ };
15048
+ const mergedSx = Array.isArray(sx)
15049
+ ? [baseSx, ...sx]
15050
+ : sx
15051
+ ? [baseSx, sx]
15052
+ : baseSx;
15053
+ return (jsxRuntimeExports.jsx(Modal, { open: open, onClose: handleClose, closeAfterTransition: true, slots: { backdrop: Backdrop }, slotProps: { backdrop: { timeout: 200 } }, children: jsxRuntimeExports.jsx(Fade, { in: open, timeout: 200, children: jsxRuntimeExports.jsx(Box, { sx: mergedSx, children: state.isLoading && !state.contactData ? (jsxRuntimeExports.jsx(Box, { sx: {
15054
+ display: 'flex',
15055
+ justifyContent: 'center',
15056
+ alignItems: 'center',
15057
+ minHeight: 180,
15058
+ }, children: jsxRuntimeExports.jsx(CircularProgress, {}) })) : state.error ? (jsxRuntimeExports.jsx(Box, { sx: { mb: 2 }, children: "Failed to load contact" })) : state.contactData ? (jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [jsxRuntimeExports.jsx(Header, { imgUrl: state.contactData.imageUrl, displayName: selectors.displayName }), jsxRuntimeExports.jsx(Box, { sx: { minHeight: 120 } })] })) : null }) }) }));
14832
15059
  };
14833
15060
 
14834
- export { ContactInfoPopup };
15061
+ export { ContactInfoPopup, EditContactModal };
14835
15062
  //# sourceMappingURL=index.js.map