customer-module-frontend 1.0.1-beta.1

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.
Files changed (87) hide show
  1. package/.cursor/rules/context.md +306 -0
  2. package/.cursor/rules/guardrails.md +35 -0
  3. package/.env +1 -0
  4. package/.github/workflows/publish-to-npm-beta.yml +51 -0
  5. package/.github/workflows/publish-to-npm.yml +58 -0
  6. package/README.md +73 -0
  7. package/eslint.config.js +23 -0
  8. package/index.html +13 -0
  9. package/package.json +43 -0
  10. package/postcss-unwrap-layers.js +31 -0
  11. package/postcss.config.js +11 -0
  12. package/public/vite.svg +1 -0
  13. package/src/App.css +40 -0
  14. package/src/App.tsx +58 -0
  15. package/src/assets/accounts.svg +3 -0
  16. package/src/assets/at_the_rate.svg +10 -0
  17. package/src/assets/buildings.svg +3 -0
  18. package/src/assets/chat.svg +3 -0
  19. package/src/assets/close.svg +3 -0
  20. package/src/assets/contacts.svg +3 -0
  21. package/src/assets/conversation.svg +10 -0
  22. package/src/assets/customers.svg +10 -0
  23. package/src/assets/details.svg +3 -0
  24. package/src/assets/domain.svg +10 -0
  25. package/src/assets/edit.svg +15 -0
  26. package/src/assets/email.svg +3 -0
  27. package/src/assets/google.svg +8 -0
  28. package/src/assets/inbox.svg +0 -0
  29. package/src/assets/message.svg +3 -0
  30. package/src/assets/no-data.svg +9 -0
  31. package/src/assets/open_in_a_new_tab.svg +10 -0
  32. package/src/assets/phone.svg +3 -0
  33. package/src/assets/react.svg +1 -0
  34. package/src/assets/search.svg +3 -0
  35. package/src/assets/search_typing.svg +4 -0
  36. package/src/assets/sm_contacts.svg +3 -0
  37. package/src/assets/sm_inbox.svg +3 -0
  38. package/src/assets/sm_slider.svg +3 -0
  39. package/src/assets/status-resolved.svg +3 -0
  40. package/src/assets/status-snoozed.svg +4 -0
  41. package/src/assets/status_open.svg +1 -0
  42. package/src/components/AccountContacts/index.tsx +107 -0
  43. package/src/components/AccountDetails/index.tsx +102 -0
  44. package/src/components/AccountsConversation/index.tsx +75 -0
  45. package/src/components/Avatar/constants.tsx +45 -0
  46. package/src/components/Avatar/index.tsx +42 -0
  47. package/src/components/BreadcrumbsSection/index.tsx +16 -0
  48. package/src/components/Card/index.tsx +31 -0
  49. package/src/components/Card/types.ts +10 -0
  50. package/src/components/ContactConversation/Converation.tsx +14 -0
  51. package/src/components/ContactConversation/index.tsx +81 -0
  52. package/src/components/ContactDetails/index.tsx +111 -0
  53. package/src/components/Contacts/EditContact.tsx +213 -0
  54. package/src/components/Contacts/constants/index.tsx +24 -0
  55. package/src/components/Contacts/index.tsx +171 -0
  56. package/src/components/ConversationBox/constants.tsx +99 -0
  57. package/src/components/ConversationBox/index.tsx +147 -0
  58. package/src/components/ConversationBox/types.ts +20 -0
  59. package/src/components/CustomersLayout/index.tsx +20 -0
  60. package/src/components/DetailsCard/index.tsx +31 -0
  61. package/src/components/DetailsCard/types.ts +10 -0
  62. package/src/components/EmptyData/NoDataFound.tsx +31 -0
  63. package/src/components/Header/index.tsx +55 -0
  64. package/src/components/Icon/index.tsx +93 -0
  65. package/src/components/Icon/types.ts +13 -0
  66. package/src/components/Listing/AccountTable.tsx +47 -0
  67. package/src/components/Listing/ContactTable.tsx +76 -0
  68. package/src/components/RightPanel/AccountPanel.tsx +123 -0
  69. package/src/components/RightPanel/ContactPanel.tsx +142 -0
  70. package/src/components/RightPanel/index.tsx +167 -0
  71. package/src/components/Search/SearchDialog.tsx +150 -0
  72. package/src/components/TabsSection/index.tsx +49 -0
  73. package/src/constants/index.tsx +645 -0
  74. package/src/hooks/useBreadcrumb.tsx +93 -0
  75. package/src/index.css +315 -0
  76. package/src/main.tsx +14 -0
  77. package/src/pages/AccountDetail.tsx +68 -0
  78. package/src/pages/Accounts.tsx +12 -0
  79. package/src/pages/ContactDetail.tsx +55 -0
  80. package/src/pages/Contacts.tsx +12 -0
  81. package/src/stores/count.tsx +17 -0
  82. package/src/types/index.ts +0 -0
  83. package/tailwind.config.js +179 -0
  84. package/tsconfig.app.json +36 -0
  85. package/tsconfig.json +7 -0
  86. package/tsconfig.node.json +26 -0
  87. package/vite.config.ts +31 -0
@@ -0,0 +1,20 @@
1
+ import { Outlet, useLocation } from "react-router-dom";
2
+ import Header from "../Header";
3
+ import TabsSection from "../TabsSection";
4
+ import BreadcrumbsSection from "../BreadcrumbsSection";
5
+
6
+ export default function CustomersLayout() {
7
+ const location = useLocation();
8
+ const isDetailsPage = location.pathname.includes("/accounts/") ||
9
+ location.pathname.includes("/contacts/");
10
+
11
+ console.log('isDetailsPage', isDetailsPage);
12
+ return (
13
+ <Header>
14
+ <div className="cm:pt-2 cm:px-12">
15
+ {isDetailsPage ? <BreadcrumbsSection /> : <TabsSection />}
16
+ </div>
17
+ <Outlet />
18
+ </Header>
19
+ );
20
+ }
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import type { DetailsCardProps } from './types';
3
+
4
+ const DetailsCard: React.FC<DetailsCardProps> = ({
5
+ header,
6
+ children,
7
+ className = '',
8
+ maxWidth = 'cm:max-w-[360px]',
9
+ maxHeight = 'cm:h-full'
10
+ }) => {
11
+ return (
12
+ <div
13
+ className={`cm:bg-slate-surface-white cm:border cm:border-slate-border-light cm:rounded-[12px] cm:relative cm:w-full ${maxWidth} ${maxHeight} ${className}`}
14
+ >
15
+ <div className="cm:flex cm:flex-col cm:items-start cm:overflow-clip cm:relative cm:rounded-[inherit] cm:w-full">
16
+ {/* Header Section */}
17
+ <div className="cm:border-b cm:border-slate-border-mild cm:box-border cm:flex cm:h-12 cm:items-center cm:pl-[21px] cm:pr-[25px] cm:py-[14px] cm:relative cm:shrink-0 cm:w-full">
18
+ {header}
19
+ </div>
20
+
21
+ {/* Content Section */}
22
+ <div className="cm:box-border cm:flex cm:flex-col cm:items-start cm:overflow-clip cm:px-[21px] cm:py-5 cm:relative cm:shrink-0 cm:w-full">
23
+ {children}
24
+ </div>
25
+ </div>
26
+ </div>
27
+ );
28
+ };
29
+
30
+ export default DetailsCard;
31
+
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface DetailsCardProps {
4
+ header: ReactNode;
5
+ children: ReactNode;
6
+ className?: string;
7
+ maxWidth?: string;
8
+ maxHeight?: string;
9
+ }
10
+
@@ -0,0 +1,31 @@
1
+ import React from 'react';
2
+ import NoDataImage from '../../assets/no-data.svg';
3
+
4
+ export interface NoDataFoundProps {
5
+ title: string;
6
+ description: string;
7
+ }
8
+
9
+ const NoDataFound: React.FC<NoDataFoundProps> = ({ title, description }) => {
10
+ return (
11
+ <div className="cm:flex cm:flex-col cm:items-center cm:justify-center cm:w-full cm:h-full cm:py-12">
12
+ <div className="cm:flex cm:flex-col cm:items-center cm:gap-3">
13
+ <img
14
+ src={NoDataImage}
15
+ alt="No data"
16
+ className="cm:w-[100px] cm:h-[100px]"
17
+ />
18
+ <div className="cm:flex cm:flex-col cm:items-center cm:gap-1">
19
+ <p className="cm:text-body-sm cm:text-slate-text-title cm:text-center">
20
+ {title}
21
+ </p>
22
+ <p className="cm:text-body-meduim cm:text-slate-text-subtle cm:text-center">
23
+ {description}
24
+ </p>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ );
29
+ };
30
+
31
+ export default NoDataFound;
@@ -0,0 +1,55 @@
1
+ import React from "react";
2
+ import type { ReactNode } from "react";
3
+ import SearchDialog from "../Search/SearchDialog";
4
+ import { TextField } from "hiver-ui-kit-extended";
5
+ import searchIcon from "@assets/search.svg";
6
+ import Icon from "../Icon";
7
+ interface LayoutProps {
8
+ children: ReactNode;
9
+ }
10
+
11
+ const Layout: React.FC<LayoutProps> = ({ children }) => {
12
+ const [isSearchDialogOpen, setIsSearchDialogOpen] = React.useState(false);
13
+
14
+ return (
15
+ <div className="cm:min-h-screen cm:bg-slate-surface-white">
16
+ {/* Header */}
17
+ <header className="cm:border-b cm:border-slate-border-light cm:bg-white cm:px-12 cm:h-14 cm:flex cm:items-center cm:justify-between">
18
+ {/* Right: Search Bar */}
19
+
20
+ <div className="cm:flex cm:items-center cm:gap-3">
21
+ <div className="cm:text-label-medium cm:text-slate-text-title">
22
+ Customers
23
+ </div>
24
+ <Icon
25
+ name="customers"
26
+ size={16}
27
+ type="active"
28
+ color="#0F172A"
29
+ />
30
+ </div>
31
+
32
+ <div className="cm:w-[300px]" onClick={()=> setIsSearchDialogOpen(true)}>
33
+ <TextField
34
+ size="small"
35
+ onChange={()=>setIsSearchDialogOpen(true)}
36
+ placeholder="Search"
37
+ icon={searchIcon}
38
+ iconPosition="left"
39
+ />
40
+
41
+
42
+ </div>
43
+ </header>
44
+
45
+ {/* Main Content */}
46
+ <main>{children}</main>
47
+ <SearchDialog
48
+ open={isSearchDialogOpen}
49
+ onClose={() => setIsSearchDialogOpen(false)}
50
+ />
51
+ </div>
52
+ );
53
+ };
54
+
55
+ export default Layout;
@@ -0,0 +1,93 @@
1
+ import { useEffect, useState } from 'react';
2
+ import type { IconProps } from './types';
3
+
4
+ const icons = import.meta.glob('/src/assets/**/*.svg', {
5
+ query: '?raw',
6
+ import: 'default',
7
+ });
8
+
9
+ const strokeWidths: Record<string, string> = { '14': '1.1', '16': '1.3' };
10
+
11
+ const Icon: React.FC<IconProps> = ({
12
+ name,
13
+ size = 16,
14
+ type = 'active',
15
+ folder = '',
16
+ disabled = false,
17
+ color,
18
+ fill,
19
+ className,
20
+ onClick,
21
+ }) => {
22
+ const [svgContent, setSvgContent] = useState<string>('');
23
+
24
+ useEffect(() => {
25
+ const loadIcon = async () => {
26
+ try {
27
+ const basePath = folder
28
+ ? `/src/assets/${folder}/${name}.svg`
29
+ : `/src/assets/${name}.svg`;
30
+
31
+ const loader = icons[basePath];
32
+ if (!loader) {
33
+ console.error(`[Icon] Icon '${name}' not found at ${basePath}`);
34
+ return;
35
+ }
36
+
37
+ const raw = (await loader()) as string;
38
+ const sizeStr = String(size);
39
+ const stroke = color ?? `var(--p-slate-icons-${disabled ? 'disabled' : type})`;
40
+ const strokeWidth = strokeWidths[sizeStr] || '1.3';
41
+
42
+ let processed = raw
43
+ .replace(/stroke=".*?"/g, `stroke="${stroke}"`)
44
+ .replace(/stroke-width=".*?"/g, `stroke-width="${strokeWidth}"`)
45
+ .replace(/<svg([^>]*?)>/, (_, attrs: string) => {
46
+ let newAttrs = attrs;
47
+
48
+ // Update or add width
49
+ if (/width=".*?"/.test(newAttrs)) {
50
+ newAttrs = newAttrs.replace(/width=".*?"/, `width="${sizeStr}"`);
51
+ } else {
52
+ newAttrs = `${newAttrs} width="${sizeStr}"`;
53
+ }
54
+
55
+ // Update or add height
56
+ if (/height=".*?"/.test(newAttrs)) {
57
+ newAttrs = newAttrs.replace(/height=".*?"/, `height="${sizeStr}"`);
58
+ } else {
59
+ newAttrs = `${newAttrs} height="${sizeStr}"`;
60
+ }
61
+
62
+ return `<svg${newAttrs} style="max-width: ${sizeStr}px;">`;
63
+ });
64
+
65
+ // Handle fill for rect elements
66
+ if (fill) {
67
+ processed = processed.replace(/<rect([^>]*?)\/?>/g, (_match, rectAttrs: string) => {
68
+ if (/fill="/.test(rectAttrs)) {
69
+ return `<rect${rectAttrs.replace(/fill=".*?"/, `fill="${fill}"`)} />`;
70
+ }
71
+ return `<rect${rectAttrs} fill="${fill}" />`;
72
+ });
73
+ }
74
+
75
+ setSvgContent(processed);
76
+ } catch (error) {
77
+ console.error(`[Icon] Error loading icon '${name}':`, error);
78
+ }
79
+ };
80
+
81
+ loadIcon();
82
+ }, [name, size, type, disabled, color, fill, folder]);
83
+
84
+ return (
85
+ <span
86
+ className={`cm:flex cm:items-center cm:justify-center cm:w-max ${disabled ? 'cm:cursor-not-allowed' : 'cm:cursor-inherit'} ${className || ''}`}
87
+ onClick={onClick}
88
+ dangerouslySetInnerHTML={{ __html: svgContent }}
89
+ />
90
+ );
91
+ };
92
+
93
+ export default Icon;
@@ -0,0 +1,13 @@
1
+ export type IconType = 'active' | 'inactive' | 'disabled';
2
+
3
+ export interface IconProps {
4
+ name: string;
5
+ size?: number;
6
+ type?: IconType;
7
+ folder?: string;
8
+ disabled?: boolean;
9
+ color?: string;
10
+ fill?: string;
11
+ className?: string;
12
+ onClick?: (event: React.MouseEvent<HTMLSpanElement>) => void;
13
+ }
@@ -0,0 +1,47 @@
1
+ import { DataTable } from "hiver-ui-kit-extended";
2
+ import { accountData } from "../../constants/index";
3
+ import { useNavigate } from 'react-router-dom';
4
+
5
+ // Transform account results to table data
6
+ const tableData = accountData.data.results.map((account) => ({
7
+ id: account.id,
8
+ name: account.name,
9
+ domain: account.domains[0]?.name || account.generated_name,
10
+ }));
11
+
12
+ const columns = [
13
+ {
14
+ id: "name",
15
+ field: "name",
16
+ label: "Account name",
17
+ },
18
+ {
19
+ id: "domain",
20
+ field: "domain",
21
+ label: "Domain",
22
+ },
23
+ ];
24
+
25
+ export default function AccountTable() {
26
+
27
+ const navigate = useNavigate();
28
+
29
+ const handleAccountClick = (event: any) => {
30
+ const data = event.data;
31
+ navigate(`/accounts/${data.id}`);
32
+ };
33
+
34
+ return (
35
+ <div className="cm:px-12">
36
+ <DataTable
37
+ scrollable={true}
38
+ scrollHeight="32.75rem"
39
+ value={tableData}
40
+ columns={columns}
41
+
42
+ // @ts-ignore
43
+ onRowClick={handleAccountClick}
44
+ />
45
+ </div>
46
+ );
47
+ }
@@ -0,0 +1,76 @@
1
+ import { DataTable } from "hiver-ui-kit-extended";
2
+ import { useNavigate } from 'react-router-dom';
3
+ import { contactsData } from "../../constants/index";
4
+ import Avatar from "../Avatar/index";
5
+
6
+ // Name cell with avatar
7
+ const NameCell = ({ name }: { name: string }) => (
8
+ <div className="flex items-center gap-2">
9
+ <Avatar name={name} labelName={name} />
10
+ <span className="text-sm text-slate-900">{name}</span>
11
+ </div>
12
+ );
13
+
14
+ // Email cell with link styling
15
+ const EmailCell = ({ email }: { email: string }) => (
16
+ <span className="text-sm !text-primary-text-default" style={{ color: '#0C3E9D' }}>{email}</span>
17
+ );
18
+
19
+ // Phone cell
20
+ const PhoneCell = ({ phone }: { phone: string }) => (
21
+ <span className="text-sm text-slate-900" style={{ color: '#334155' }}>{phone}</span>
22
+ );
23
+
24
+ // Account cell
25
+ const AccountCell = ({ account }: { account: string }) => (
26
+ <span className="text-sm text-slate-900">{account}</span>
27
+ );
28
+
29
+ // Transform data with custom renderers
30
+ const tableData = contactsData.data.results.map((result) => ({
31
+ ...result,
32
+ nameDisplay: <NameCell name={result.name} />,
33
+ emailDisplay: <EmailCell email={result.email} />,
34
+ phoneDisplay: <PhoneCell phone={result.phone_number || ""} />,
35
+ accountDisplay: <AccountCell account={result.company?.name || ""} />,
36
+ }));
37
+
38
+ const columns = [
39
+ {
40
+ id: "name",
41
+ field: "nameDisplay",
42
+ label: "Name",
43
+ },
44
+ {
45
+ id: "email",
46
+ field: "emailDisplay",
47
+ label: "Email",
48
+ },
49
+ {
50
+ id: "phone",
51
+ field: "phoneDisplay",
52
+ label: "Phone Number",
53
+ },
54
+ {
55
+ id: "account",
56
+ field: "accountDisplay",
57
+ label: "Account",
58
+ },
59
+ ];
60
+
61
+ export default function ContactTable() {
62
+ const navigate = useNavigate();
63
+
64
+ const handleContactClick = (event: any) => {
65
+ const data = event.data;
66
+ navigate(`/contacts/${data.id}`);
67
+ };
68
+
69
+ return (
70
+ <div className="cm:px-12">
71
+ <DataTable scrollable={true} scrollHeight="32.75rem" value={tableData} columns={columns}
72
+ // @ts-ignore
73
+ onRowClick={handleContactClick} />
74
+ </div>
75
+ );
76
+ }
@@ -0,0 +1,123 @@
1
+ import React from 'react';
2
+ import ConversationBox from '../ConversationBox';
3
+ import Icon from '../Icon';
4
+
5
+ export interface AccountInfo {
6
+ name: string;
7
+ domain: string;
8
+ contactCount: number;
9
+ }
10
+
11
+ export interface Conversation {
12
+ id: string;
13
+ type: 'chat' | 'email';
14
+ status: 'open' | 'pending' | 'closed';
15
+ senderName: string;
16
+ timestamp: string;
17
+ heading?: string;
18
+ messagePreview: string;
19
+ mailboxName?: string;
20
+ assignedAgent?: {
21
+ name: string;
22
+ avatarColor?: string;
23
+ };
24
+ }
25
+
26
+ export interface AccountPanelProps {
27
+ account: AccountInfo;
28
+ conversations: Conversation[];
29
+ onConversationClick?: (conversationId: string) => void;
30
+ }
31
+
32
+ const AccountPanel: React.FC<AccountPanelProps> = ({
33
+ account,
34
+ conversations,
35
+ onConversationClick,
36
+ }) => {
37
+ return (
38
+ <div className="cm:flex cm:flex-col cm:gap-3 cm:w-full">
39
+ {/* Account Header */}
40
+ <div className="cm:flex cm:flex-col cm:gap-4 cm:px-4 cm:pt-6 cm:pb-4">
41
+ {/* Icon and Account Name */}
42
+ <div className="cm:flex cm:gap-3 cm:items-center">
43
+ <div className="cm:w-7 cm:h-7 cm:rounded-full cm:flex cm:items-center cm:justify-center cm:shrink-0 cm:bg-slate-100">
44
+ <Icon name="buildings" size={14} color="#94A3B8" />
45
+ </div>
46
+ <h3 className="cm:text-base cm:font-medium cm:text-slate-text-title cm:leading-5">
47
+ {account.name}
48
+ </h3>
49
+ </div>
50
+
51
+ {/* Account Details */}
52
+ <div className="cm:flex cm:flex-col cm:gap-1">
53
+ {/* Domain */}
54
+ <AccountDetailRow
55
+ icon="domain"
56
+ label="Domain"
57
+ value={account.domain}
58
+ />
59
+
60
+ {/* Contact Count */}
61
+ <AccountDetailRow
62
+ icon="contacts"
63
+ label="Contact"
64
+ value={String(account.contactCount)}
65
+ />
66
+ </div>
67
+ </div>
68
+
69
+ {/* Divider */}
70
+ <div className="cm:h-px cm:bg-slate-border-light" />
71
+
72
+ {/* Conversations Section */}
73
+ <div className="cm:flex cm:flex-col cm:gap-3">
74
+ <h4 className="cm:px-4 cm:text-sm cm:font-medium cm:text-slate-text-title cm:leading-5 cm:text-left">
75
+ Conversations from {account.name}
76
+ </h4>
77
+
78
+ {/* Conversations List */}
79
+ <div className="cm:flex cm:flex-col cm:gap-2 cm:px-4">
80
+ {conversations.map((conversation) => (
81
+ <ConversationBox
82
+ key={conversation.id}
83
+ type={conversation.type}
84
+ status={conversation.status}
85
+ size="small"
86
+ senderName={conversation.senderName}
87
+ timestamp={conversation.timestamp}
88
+ heading={conversation.heading}
89
+ messagePreview={conversation.messagePreview}
90
+ mailboxName={conversation.mailboxName}
91
+ assignedAgent={conversation.assignedAgent}
92
+ onClick={() => onConversationClick?.(conversation.id)}
93
+ />
94
+ ))}
95
+ </div>
96
+ </div>
97
+ </div>
98
+ );
99
+ };
100
+
101
+ interface AccountDetailRowProps {
102
+ icon: string;
103
+ label: string;
104
+ value: string;
105
+ }
106
+
107
+ const AccountDetailRow: React.FC<AccountDetailRowProps> = ({ icon, label, value }) => {
108
+ return (
109
+ <div className="cm:flex cm:gap-3 cm:items-center cm:h-[38px]">
110
+ <div className="cm:flex cm:gap-2 cm:items-center cm:min-w-[90px]">
111
+ <Icon name={icon} size={14} color="#64758b" />
112
+ <span className="cm:text-sm cm:text-slate-text-subtle cm:leading-5">
113
+ {label}
114
+ </span>
115
+ </div>
116
+ <span className="cm:text-sm cm:text-slate-text-body cm:leading-5 cm:truncate">
117
+ {value}
118
+ </span>
119
+ </div>
120
+ );
121
+ };
122
+
123
+ export default AccountPanel;
@@ -0,0 +1,142 @@
1
+ import React from 'react';
2
+ import ConversationBox from '../ConversationBox';
3
+ import Icon from '../Icon';
4
+ import Avatar from '../Avatar';
5
+ import PrimaryContactsDialog from '../Contacts';
6
+
7
+ export interface ContactInfo {
8
+ name: string;
9
+ email: string;
10
+ phone: string;
11
+ account: string;
12
+ avatarColor?: string;
13
+ }
14
+
15
+ export interface Conversation {
16
+ id: string;
17
+ type: 'chat' | 'email';
18
+ status: 'open' | 'pending' | 'closed';
19
+ senderName: string;
20
+ timestamp: string;
21
+ heading?: string;
22
+ messagePreview: string;
23
+ mailboxName?: string;
24
+ assignedAgent?: {
25
+ name: string;
26
+ avatarColor?: string;
27
+ };
28
+ }
29
+
30
+ export interface ContactPanelProps {
31
+ contact: ContactInfo;
32
+ conversations: Conversation[];
33
+ onConversationClick?: (conversationId: string) => void;
34
+ }
35
+
36
+ const ContactPanel: React.FC<ContactPanelProps> = ({
37
+ contact,
38
+ conversations,
39
+ onConversationClick,
40
+ }) => {
41
+ return (
42
+ <div className="cm:flex cm:flex-col cm:gap-3 cm:w-full cm:max-w-[330px]">
43
+ {/* Contact Header */}
44
+ <div className="cm:flex cm:flex-col cm:gap-4 cm:px-4 cm:pt-6 cm:pb-5">
45
+ {/* Avatar and Name */}
46
+
47
+ <div className="cm:flex cm:items-center cm:justify-between cm:px-0 cm:rounded-[6px] cm:shrink-0">
48
+ <div className="cm:flex cm:gap-3 cm:items-center">
49
+ <Avatar
50
+ name={contact.name}
51
+ labelName={contact.name}
52
+ size="default"
53
+ />
54
+
55
+ <h3 className="cm:text-base cm:font-medium cm:text-slate-text-title cm:leading-5">
56
+ {contact.name}
57
+ </h3>
58
+ </div>
59
+ {/* <EditContact /> */}
60
+ <PrimaryContactsDialog />
61
+ </div>
62
+
63
+ {/* Contact Details */}
64
+ <div className="cm:flex cm:flex-col cm:gap-1">
65
+ {/* Email */}
66
+ <ContactDetailRow
67
+ icon="email"
68
+ label="Email"
69
+ value={contact.email}
70
+ />
71
+
72
+ {/* Phone */}
73
+ <ContactDetailRow
74
+ icon="phone"
75
+ label="Phone"
76
+ value={contact.phone}
77
+ />
78
+
79
+ {/* Account */}
80
+ <ContactDetailRow
81
+ icon="buildings"
82
+ label="Account"
83
+ value={contact.account}
84
+ />
85
+ </div>
86
+ </div>
87
+
88
+ {/* Divider */}
89
+ <div className="cm:h-px cm:bg-slate-border-light" />
90
+
91
+ {/* Conversations Section */}
92
+ <div className="cm:flex cm:flex-col cm:gap-3 ">
93
+ <h4 className="cm:text-sm cm:font-medium cm:text-slate-text-title cm:leading-5 cm:text-left cm:px-4">
94
+ Conversations with {contact.name}
95
+ </h4>
96
+
97
+ {/* Conversations List */}
98
+ <div className="cm:flex cm:flex-col cm:gap-3 cm:px-4">
99
+ {conversations.map((conversation) => (
100
+ <ConversationBox
101
+ key={conversation.id}
102
+ type={conversation.type}
103
+ status={conversation.status}
104
+ size="small"
105
+ senderName={conversation.senderName}
106
+ timestamp={conversation.timestamp}
107
+ heading={conversation.heading}
108
+ messagePreview={conversation.messagePreview}
109
+ mailboxName={conversation.mailboxName}
110
+ assignedAgent={conversation.assignedAgent}
111
+ onClick={() => onConversationClick?.(conversation.id)}
112
+ />
113
+ ))}
114
+ </div>
115
+ </div>
116
+ </div>
117
+ );
118
+ };
119
+
120
+ interface ContactDetailRowProps {
121
+ icon: string;
122
+ label: string;
123
+ value: string;
124
+ }
125
+
126
+ const ContactDetailRow: React.FC<ContactDetailRowProps> = ({ icon, label, value }) => {
127
+ return (
128
+ <div className="cm:flex cm:gap-3 cm:items-center cm:h-[38px]">
129
+ <div className="cm:flex cm:gap-2 cm:items-center cm:w-[90px] cm:shrink-0">
130
+ <Icon name={icon} size={14} color="#64758b" />
131
+ <span className="cm:text-sm cm:text-slate-text-subtle cm:leading-5">
132
+ {label}
133
+ </span>
134
+ </div>
135
+ <span className="cm:text-sm cm:text-slate-text-body cm:leading-5 cm:truncate">
136
+ {value}
137
+ </span>
138
+ </div>
139
+ );
140
+ };
141
+
142
+ export default ContactPanel;