customer-module-frontend 1.0.1-beta.1 → 1.0.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.
- package/dist/customer-module.css +1 -0
- package/dist/customer-module.js +29033 -0
- package/dist/customer-module.js.map +1 -0
- package/package.json +21 -2
- package/.cursor/rules/context.md +0 -306
- package/.cursor/rules/guardrails.md +0 -35
- package/.env +0 -1
- package/.github/workflows/publish-to-npm-beta.yml +0 -51
- package/.github/workflows/publish-to-npm.yml +0 -58
- package/eslint.config.js +0 -23
- package/index.html +0 -13
- package/postcss-unwrap-layers.js +0 -31
- package/postcss.config.js +0 -11
- package/src/App.css +0 -40
- package/src/App.tsx +0 -58
- package/src/assets/accounts.svg +0 -3
- package/src/assets/at_the_rate.svg +0 -10
- package/src/assets/buildings.svg +0 -3
- package/src/assets/chat.svg +0 -3
- package/src/assets/close.svg +0 -3
- package/src/assets/contacts.svg +0 -3
- package/src/assets/conversation.svg +0 -10
- package/src/assets/customers.svg +0 -10
- package/src/assets/details.svg +0 -3
- package/src/assets/domain.svg +0 -10
- package/src/assets/edit.svg +0 -15
- package/src/assets/email.svg +0 -3
- package/src/assets/google.svg +0 -8
- package/src/assets/inbox.svg +0 -0
- package/src/assets/message.svg +0 -3
- package/src/assets/no-data.svg +0 -9
- package/src/assets/open_in_a_new_tab.svg +0 -10
- package/src/assets/phone.svg +0 -3
- package/src/assets/react.svg +0 -1
- package/src/assets/search.svg +0 -3
- package/src/assets/search_typing.svg +0 -4
- package/src/assets/sm_contacts.svg +0 -3
- package/src/assets/sm_inbox.svg +0 -3
- package/src/assets/sm_slider.svg +0 -3
- package/src/assets/status-resolved.svg +0 -3
- package/src/assets/status-snoozed.svg +0 -4
- package/src/assets/status_open.svg +0 -1
- package/src/components/AccountContacts/index.tsx +0 -107
- package/src/components/AccountDetails/index.tsx +0 -102
- package/src/components/AccountsConversation/index.tsx +0 -75
- package/src/components/Avatar/constants.tsx +0 -45
- package/src/components/Avatar/index.tsx +0 -42
- package/src/components/BreadcrumbsSection/index.tsx +0 -16
- package/src/components/Card/index.tsx +0 -31
- package/src/components/Card/types.ts +0 -10
- package/src/components/ContactConversation/Converation.tsx +0 -14
- package/src/components/ContactConversation/index.tsx +0 -81
- package/src/components/ContactDetails/index.tsx +0 -111
- package/src/components/Contacts/EditContact.tsx +0 -213
- package/src/components/Contacts/constants/index.tsx +0 -24
- package/src/components/Contacts/index.tsx +0 -171
- package/src/components/ConversationBox/constants.tsx +0 -99
- package/src/components/ConversationBox/index.tsx +0 -147
- package/src/components/ConversationBox/types.ts +0 -20
- package/src/components/CustomersLayout/index.tsx +0 -20
- package/src/components/DetailsCard/index.tsx +0 -31
- package/src/components/DetailsCard/types.ts +0 -10
- package/src/components/EmptyData/NoDataFound.tsx +0 -31
- package/src/components/Header/index.tsx +0 -55
- package/src/components/Icon/index.tsx +0 -93
- package/src/components/Icon/types.ts +0 -13
- package/src/components/Listing/AccountTable.tsx +0 -47
- package/src/components/Listing/ContactTable.tsx +0 -76
- package/src/components/RightPanel/AccountPanel.tsx +0 -123
- package/src/components/RightPanel/ContactPanel.tsx +0 -142
- package/src/components/RightPanel/index.tsx +0 -167
- package/src/components/Search/SearchDialog.tsx +0 -150
- package/src/components/TabsSection/index.tsx +0 -49
- package/src/constants/index.tsx +0 -645
- package/src/hooks/useBreadcrumb.tsx +0 -93
- package/src/index.css +0 -315
- package/src/main.tsx +0 -14
- package/src/pages/AccountDetail.tsx +0 -68
- package/src/pages/Accounts.tsx +0 -12
- package/src/pages/ContactDetail.tsx +0 -55
- package/src/pages/Contacts.tsx +0 -12
- package/src/stores/count.tsx +0 -17
- package/src/types/index.ts +0 -0
- package/tailwind.config.js +0 -179
- package/tsconfig.app.json +0 -36
- package/tsconfig.json +0 -7
- package/tsconfig.node.json +0 -26
- package/vite.config.ts +0 -31
- /package/{public → dist}/vite.svg +0 -0
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Dialog, Button, TextField } from "hiver-ui-kit-extended";
|
|
3
|
-
import { mockContacts } from "./constants";
|
|
4
|
-
import Avatar from "../Avatar";
|
|
5
|
-
import closeIcon from "../../assets/close.svg";
|
|
6
|
-
import Icon from "../Icon";
|
|
7
|
-
|
|
8
|
-
type DialogState = "add-primary-contact" | "primary-contact-confirmation";
|
|
9
|
-
|
|
10
|
-
interface Contact {
|
|
11
|
-
id: string;
|
|
12
|
-
name: string;
|
|
13
|
-
email: string;
|
|
14
|
-
isSelected?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const PrimaryContactsDialog = () => {
|
|
18
|
-
const [dialogOpen, setDialogOpen] = useState(false);
|
|
19
|
-
const [dialogState, setDialogState] = useState<DialogState>("add-primary-contact");
|
|
20
|
-
const [searchValue, setSearchValue] = useState("");
|
|
21
|
-
const [selectedContact, setSelectedContact] = useState<Contact | null>(null);
|
|
22
|
-
|
|
23
|
-
const handleClick = () => {
|
|
24
|
-
setDialogOpen(true);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const handleClose = () => {
|
|
28
|
-
setDialogOpen(false);
|
|
29
|
-
setDialogState("add-primary-contact");
|
|
30
|
-
setSearchValue("");
|
|
31
|
-
setSelectedContact(null);
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
const filteredContacts = mockContacts.filter(
|
|
35
|
-
(contact) =>
|
|
36
|
-
contact.name.toLowerCase().includes(searchValue.toLowerCase()) ||
|
|
37
|
-
contact.email.toLowerCase().includes(searchValue.toLowerCase())
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
const handleContactSelect = (contact: Contact) => {
|
|
41
|
-
setSelectedContact(contact);
|
|
42
|
-
setDialogState("primary-contact-confirmation");
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const renderHeader = () => {
|
|
46
|
-
if (dialogState === "add-primary-contact") {
|
|
47
|
-
return (
|
|
48
|
-
<div className="flex items-center justify-between w-full">
|
|
49
|
-
<div>Change Primary Contact</div>
|
|
50
|
-
<Button
|
|
51
|
-
onClick={handleClose}
|
|
52
|
-
variant="text"
|
|
53
|
-
icon={closeIcon}
|
|
54
|
-
size="small"
|
|
55
|
-
color="primary"
|
|
56
|
-
className="p-0"
|
|
57
|
-
/>
|
|
58
|
-
</div>
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
return <div>Primary Contact Confirmation</div>;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const renderContent = () => {
|
|
65
|
-
if (dialogState === "primary-contact-confirmation") {
|
|
66
|
-
return (
|
|
67
|
-
<div className="w-[380px]" onClick={(e) => e.stopPropagation()}>
|
|
68
|
-
<p className="font-['Hanken_Grotesk',sans-serif] font-normal leading-5 text-sm text-[var(--slate-text-subtle,#64758b)] m-0 w-full mb-4">
|
|
69
|
-
Are you sure you want to change the contact to {selectedContact?.name}? This will replace
|
|
70
|
-
the existing contact for this conversation.
|
|
71
|
-
</p>
|
|
72
|
-
<div className="flex gap-3 items-center justify-end w-full">
|
|
73
|
-
<Button
|
|
74
|
-
variant="outlined"
|
|
75
|
-
size="medium"
|
|
76
|
-
onClick={() => setDialogState("add-primary-contact")}
|
|
77
|
-
>
|
|
78
|
-
Cancel
|
|
79
|
-
</Button>
|
|
80
|
-
<Button
|
|
81
|
-
onClick={() => {
|
|
82
|
-
alert("Contact changed!");
|
|
83
|
-
handleClose();
|
|
84
|
-
}}
|
|
85
|
-
size="medium"
|
|
86
|
-
>
|
|
87
|
-
Change
|
|
88
|
-
</Button>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return (
|
|
95
|
-
<div className="w-[380px]" onClick={(e) => e.stopPropagation()}>
|
|
96
|
-
<div className="w-full mb-4">
|
|
97
|
-
<TextField
|
|
98
|
-
placeholder="Search name or email"
|
|
99
|
-
value={searchValue}
|
|
100
|
-
onChange={(e) => setSearchValue(e.target.value)}
|
|
101
|
-
/>
|
|
102
|
-
</div>
|
|
103
|
-
<div className="flex flex-col gap-2 items-start w-full">
|
|
104
|
-
<p className="font-['Hanken_Grotesk',sans-serif] font-normal leading-5 text-sm text-[#64758b]">
|
|
105
|
-
Contacts from this conversation
|
|
106
|
-
</p>
|
|
107
|
-
<div className="flex flex-col items-start w-full gap-0">
|
|
108
|
-
{filteredContacts.map((contact: Contact) => (
|
|
109
|
-
<ContactItem
|
|
110
|
-
key={contact.id}
|
|
111
|
-
contact={contact}
|
|
112
|
-
onSelect={() => handleContactSelect(contact)}
|
|
113
|
-
/>
|
|
114
|
-
))}
|
|
115
|
-
</div>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
);
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<div>
|
|
123
|
-
|
|
124
|
-
<Icon name="edit" size={16} type="active" color="#0F172A" onClick={handleClick} />
|
|
125
|
-
<Dialog
|
|
126
|
-
open={dialogOpen}
|
|
127
|
-
variant="confirmation"
|
|
128
|
-
onClose={handleClose}
|
|
129
|
-
onBack={() => setDialogState("add-primary-contact")}
|
|
130
|
-
maxWidth="sm"
|
|
131
|
-
title={renderHeader()}
|
|
132
|
-
>
|
|
133
|
-
{renderContent()}
|
|
134
|
-
</Dialog>
|
|
135
|
-
</div>
|
|
136
|
-
);
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
export default PrimaryContactsDialog;
|
|
140
|
-
|
|
141
|
-
const ContactItem = ({
|
|
142
|
-
contact,
|
|
143
|
-
onSelect,
|
|
144
|
-
}: {
|
|
145
|
-
contact: Contact;
|
|
146
|
-
onSelect: () => void;
|
|
147
|
-
}) => {
|
|
148
|
-
return (
|
|
149
|
-
<div
|
|
150
|
-
className="bg-white flex gap-3 items-center px-0 py-2 rounded-lg w-full cursor-pointer hover:bg-gray-50 transition-colors"
|
|
151
|
-
onClick={onSelect}
|
|
152
|
-
>
|
|
153
|
-
<div className="flex gap-3 items-center flex-1 min-w-0">
|
|
154
|
-
<Avatar name={contact.name} labelName={contact.name} />
|
|
155
|
-
<div className="flex flex-col items-start justify-center flex-1 min-w-0">
|
|
156
|
-
<p className="font-['Hanken_Grotesk',sans-serif] font-normal leading-5 text-sm text-[#334155] truncate w-full">
|
|
157
|
-
{contact.name}
|
|
158
|
-
</p>
|
|
159
|
-
<p className="font-['Hanken_Grotesk',sans-serif] font-normal leading-[18px] text-xs text-[#64758b] truncate w-full">
|
|
160
|
-
{contact.email}
|
|
161
|
-
</p>
|
|
162
|
-
</div>
|
|
163
|
-
</div>
|
|
164
|
-
{contact.isSelected && (
|
|
165
|
-
<div className="overflow-clip relative shrink-0 w-3.5 h-3.5">
|
|
166
|
-
<i className="pi pi-check text-[#276cf0] text-sm"></i>
|
|
167
|
-
</div>
|
|
168
|
-
)}
|
|
169
|
-
</div>
|
|
170
|
-
);
|
|
171
|
-
};
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { ConversationStatus, ConversationBoxSize } from "./types";
|
|
3
|
-
import statusOpenIcon from '../../assets/status_open.svg';
|
|
4
|
-
import statusPendingIcon from '../../assets/status-snoozed.svg';
|
|
5
|
-
import statusClosedIcon from '../../assets/status-resolved.svg';
|
|
6
|
-
import conversationIcon from '../../assets/conversation.svg';
|
|
7
|
-
import emailIcon from '../../assets/email.svg';
|
|
8
|
-
import sharedMailboxIcon from '../../assets/sm_inbox.svg';
|
|
9
|
-
|
|
10
|
-
// Status configuration with colors and icons
|
|
11
|
-
export const statusConfig: Record<ConversationStatus, { label: string; color: string; iconColor: string; icon: string }> = {
|
|
12
|
-
open: { label: 'Open', color: '#E37144', iconColor: '#E3692C', icon: statusOpenIcon },
|
|
13
|
-
pending: { label: 'Pending', color: '#5398CF', iconColor: '#EC8A54', icon: statusPendingIcon },
|
|
14
|
-
closed: { label: 'Closed', color: '#2DBB6D', iconColor: '#276CF0', icon: statusClosedIcon },
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// Size configuration
|
|
18
|
-
export const sizeConfig: Record<ConversationBoxSize, {
|
|
19
|
-
padding: { email: string; chat: string };
|
|
20
|
-
gap: string;
|
|
21
|
-
iconSize: string;
|
|
22
|
-
fontSize: { title: string; body: string; timestamp: string };
|
|
23
|
-
avatarSize: string;
|
|
24
|
-
avatarText: string;
|
|
25
|
-
maxWidth: string;
|
|
26
|
-
dividerHeight: string;
|
|
27
|
-
}> = {
|
|
28
|
-
default: {
|
|
29
|
-
padding: {
|
|
30
|
-
email: 'cm:px-5 cm:py-4',
|
|
31
|
-
chat: 'cm:px-5 cm:py-4',
|
|
32
|
-
},
|
|
33
|
-
gap: 'cm:gap-1.5',
|
|
34
|
-
iconSize: 'cm:w-3.5 cm:h-3.5',
|
|
35
|
-
fontSize: {
|
|
36
|
-
title: 'cm:text-sm',
|
|
37
|
-
body: 'cm:text-sm',
|
|
38
|
-
timestamp: 'cm:text-[13px]',
|
|
39
|
-
},
|
|
40
|
-
avatarSize: 'cm:w-4 cm:h-4',
|
|
41
|
-
avatarText: 'cm:text-[9px]',
|
|
42
|
-
maxWidth: 'cm:max-w-[914px]',
|
|
43
|
-
dividerHeight: 'cm:h-4',
|
|
44
|
-
},
|
|
45
|
-
small: {
|
|
46
|
-
padding: {
|
|
47
|
-
email: 'cm:p-4',
|
|
48
|
-
chat: 'cm:p-4',
|
|
49
|
-
},
|
|
50
|
-
gap: 'cm:gap-1',
|
|
51
|
-
iconSize: 'cm:w-3 cm:h-3',
|
|
52
|
-
fontSize: {
|
|
53
|
-
title: 'cm:text-xs',
|
|
54
|
-
body: 'cm:text-xs',
|
|
55
|
-
timestamp: 'cm:text-xs',
|
|
56
|
-
},
|
|
57
|
-
avatarSize: 'cm:w-3 cm:h-3',
|
|
58
|
-
avatarText: 'cm:text-[8px]',
|
|
59
|
-
maxWidth: 'cm:max-w-[330px]',
|
|
60
|
-
dividerHeight: 'cm:h-3',
|
|
61
|
-
},
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// Icon components
|
|
65
|
-
interface IconProps {
|
|
66
|
-
iconSize: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export const ChatIcon: React.FC<IconProps> = ({ iconSize }) => (
|
|
70
|
-
<img
|
|
71
|
-
src={conversationIcon}
|
|
72
|
-
alt="Chat"
|
|
73
|
-
className={iconSize}
|
|
74
|
-
/>
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
export const EnvelopeIcon: React.FC<IconProps> = ({ iconSize }) => (
|
|
78
|
-
<img
|
|
79
|
-
src={emailIcon}
|
|
80
|
-
alt="Email"
|
|
81
|
-
className={iconSize}
|
|
82
|
-
/>
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
export const SharedMailboxIcon: React.FC<IconProps> = ({ iconSize }) => (
|
|
86
|
-
<img
|
|
87
|
-
src={sharedMailboxIcon}
|
|
88
|
-
alt="Shared Mailbox"
|
|
89
|
-
className={iconSize}
|
|
90
|
-
/>
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
export const StatusIcon: React.FC<IconProps & { icon: string; label: string }> = ({ iconSize, icon, label }) => (
|
|
94
|
-
<img
|
|
95
|
-
src={icon}
|
|
96
|
-
alt={label}
|
|
97
|
-
className={iconSize}
|
|
98
|
-
/>
|
|
99
|
-
);
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react';
|
|
2
|
-
import type { ConversationBoxProps } from './types';
|
|
3
|
-
import { statusConfig, sizeConfig, ChatIcon, EnvelopeIcon, SharedMailboxIcon, StatusIcon } from './constants';
|
|
4
|
-
import Avatar from '../Avatar';
|
|
5
|
-
import openInANewTab from '../../assets/open_in_a_new_tab.svg';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const ConversationBox: React.FC<ConversationBoxProps> = ({
|
|
9
|
-
type,
|
|
10
|
-
status,
|
|
11
|
-
size = 'default',
|
|
12
|
-
senderName,
|
|
13
|
-
timestamp,
|
|
14
|
-
messagePreview,
|
|
15
|
-
heading,
|
|
16
|
-
mailboxName,
|
|
17
|
-
assignedAgent,
|
|
18
|
-
onClick,
|
|
19
|
-
}) => {
|
|
20
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
21
|
-
const statusInfo = statusConfig[status];
|
|
22
|
-
const sizeStyles = sizeConfig[size];
|
|
23
|
-
|
|
24
|
-
// Divider line (vertical)
|
|
25
|
-
const Divider = () => (
|
|
26
|
-
<div className={`${sizeStyles.dividerHeight} cm:w-px cm:bg-slate-border-light cm:shrink-0`} />
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
const handleActionClick = (e: React.MouseEvent) => {
|
|
30
|
-
e.stopPropagation(); // Prevent triggering the onClick handler
|
|
31
|
-
// Handle action button click (e.g., show menu)
|
|
32
|
-
console.log('Action button clicked');
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div
|
|
37
|
-
className={`cm:border cm:p-3.5 cm:border-slate-border-light cm:rounded-[10px] cm:flex cm:flex-col ${sizeStyles.gap} cm:w-full ${sizeStyles.maxWidth} cm:cursor-pointer hover:cm:bg-slate-surface-subtle_100 cm:transition-colors cm:relative ${type === 'email' ? sizeStyles.padding.email : sizeStyles.padding.chat
|
|
38
|
-
}`}
|
|
39
|
-
onClick={onClick}
|
|
40
|
-
onMouseEnter={() => setIsHovered(true)}
|
|
41
|
-
onMouseLeave={() => setIsHovered(false)}
|
|
42
|
-
>
|
|
43
|
-
{/* Top row: Sender, Status, Timestamp */}
|
|
44
|
-
<div className={`cm:flex ${size === 'small' ? 'cm:gap-2' : 'cm:gap-2.5'} cm:items-center cm:w-full`}>
|
|
45
|
-
<div className={`cm:flex-1 cm:flex ${size === 'small' ? 'cm:gap-2' : 'cm:gap-2.5'} cm:items-center cm:min-w-0`}>
|
|
46
|
-
{/* Icon and Sender Name */}
|
|
47
|
-
<div className={`cm:flex ${size === 'small' ? 'cm:gap-1.5' : 'cm:gap-2'} cm:items-center cm:shrink-0`}>
|
|
48
|
-
<div className={`${sizeStyles.iconSize} cm:shrink-0 cm:flex cm:items-center cm:justify-center cm:text-slate-icons-subtle`}>
|
|
49
|
-
{type === 'chat' ? <ChatIcon iconSize={sizeStyles.iconSize} /> : <EnvelopeIcon iconSize={sizeStyles.iconSize} />}
|
|
50
|
-
</div>
|
|
51
|
-
<div className="cm:flex cm:gap-1 cm:items-center">
|
|
52
|
-
<p className={`cm:font-medium cm:leading-5 ${sizeStyles.fontSize.title} cm:text-slate-text-title cm:whitespace-nowrap`}>
|
|
53
|
-
{senderName}
|
|
54
|
-
</p>
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
|
|
58
|
-
{/* Divider */}
|
|
59
|
-
<Divider />
|
|
60
|
-
|
|
61
|
-
{/* Status Chip */}
|
|
62
|
-
<div className={`cm:flex ${size === 'small' ? 'cm:gap-1.5' : 'cm:gap-2'} cm:items-center cm:justify-center cm:rounded-2xl cm:shrink-0`}>
|
|
63
|
-
<div className={`${sizeStyles.iconSize} cm:shrink-0 cm:flex cm:items-center cm:justify-center`}>
|
|
64
|
-
<StatusIcon iconSize={sizeStyles.iconSize} icon={statusInfo.icon} label={statusInfo.label} />
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
{/* Timestamp or Action Button - fixed width container to prevent layout shift */}
|
|
70
|
-
<div className="cm:relative cm:shrink-0 cm:min-w-[100px] cm:flex cm:items-center cm:justify-end">
|
|
71
|
-
{/* Timestamp - always rendered but hidden on hover */}
|
|
72
|
-
<p
|
|
73
|
-
className={`cm:font-normal cm:leading-[18px] ${sizeStyles.fontSize.timestamp} cm:text-slate-text-subtle cm:whitespace-nowrap cm:text-right cm:transition-opacity ${
|
|
74
|
-
isHovered ? 'cm:opacity-0' : 'cm:opacity-100'
|
|
75
|
-
}`}
|
|
76
|
-
>
|
|
77
|
-
{timestamp}
|
|
78
|
-
</p>
|
|
79
|
-
|
|
80
|
-
{/* Action Button - absolutely positioned, shown on hover */}
|
|
81
|
-
<button
|
|
82
|
-
onClick={handleActionClick}
|
|
83
|
-
className={`cm:absolute cm:right-0 cm:bg-slate-surface-white cm:border cm:border-slate-border-light cm:rounded-[6px] cm:flex cm:items-center cm:justify-center cm:p-1.5 cm:w-7 cm:h-7 cm:hover:bg-slate-surface-subtle cm:transition-all ${
|
|
84
|
-
isHovered ? 'cm:opacity-100 cm:pointer-events-auto' : 'cm:opacity-0 cm:pointer-events-none'
|
|
85
|
-
}`}
|
|
86
|
-
aria-label="Open in a new tab"
|
|
87
|
-
>
|
|
88
|
-
<div className="cm:w-3.5 cm:h-3.5 cm:flex cm:items-center cm:justify-center">
|
|
89
|
-
<img src={openInANewTab} alt="Open in a new tab" className="cm:w-3.5 cm:h-3.5" />
|
|
90
|
-
</div>
|
|
91
|
-
</button>
|
|
92
|
-
</div>
|
|
93
|
-
</div>
|
|
94
|
-
|
|
95
|
-
{/* Content Section */}
|
|
96
|
-
<div className="cm:flex cm:flex-col cm:items-start cm:w-full">
|
|
97
|
-
{/* Heading (only for email) */}
|
|
98
|
-
{type === 'email' && heading && (
|
|
99
|
-
<div className={`cm:flex ${size === 'small' ? 'cm:gap-2' : 'cm:gap-3'} cm:items-center cm:w-full`}>
|
|
100
|
-
<div className={`cm:flex-1 cm:flex cm:flex-col cm:font-normal cm:justify-center cm:leading-none cm:overflow-hidden ${sizeStyles.fontSize.body} cm:text-slate-text-body`}>
|
|
101
|
-
<p className="cm:leading-5 cm:text-left cm:overflow-ellipsis cm:overflow-hidden">
|
|
102
|
-
{heading}
|
|
103
|
-
</p>
|
|
104
|
-
</div>
|
|
105
|
-
</div>
|
|
106
|
-
)}
|
|
107
|
-
|
|
108
|
-
{/* Message Preview and Footer */}
|
|
109
|
-
<div className={`cm:flex ${size === 'small' ? 'cm:flex-col cm:gap-1' : 'cm:flex-row cm:items-center cm:justify-between'} cm:w-full`}>
|
|
110
|
-
<p className={`cm:font-normal cm:leading-5 cm:overflow-ellipsis cm:overflow-hidden ${sizeStyles.fontSize.body} cm:text-slate-text-subtle cm:whitespace-nowrap ${size === 'small' ? 'cm:w-full' : 'cm:max-w-[560px]'}`}>
|
|
111
|
-
{messagePreview}
|
|
112
|
-
</p>
|
|
113
|
-
|
|
114
|
-
{/* MailboxName and Assigned Agent */}
|
|
115
|
-
<div className={`cm:flex ${size === 'small' ? 'cm:gap-2 cm:w-full' : 'cm:gap-2.5'} cm:items-center cm:shrink-0`}>
|
|
116
|
-
{/* mailboxName */}
|
|
117
|
-
{mailboxName && (
|
|
118
|
-
<>
|
|
119
|
-
<div className="cm:flex cm:gap-1 cm:items-center cm:justify-center cm:py-0.5 cm:rounded cm:shrink-0">
|
|
120
|
-
<div className={`${sizeStyles.iconSize} cm:overflow-hidden cm:shrink-0 cm:flex cm:items-center cm:justify-center cm:text-slate-icons-subtle`}>
|
|
121
|
-
<SharedMailboxIcon iconSize={sizeStyles.iconSize} />
|
|
122
|
-
</div>
|
|
123
|
-
<p className={`cm:font-normal cm:leading-5 ${sizeStyles.fontSize.body} cm:text-slate-text-subtle cm:whitespace-nowrap`}>
|
|
124
|
-
{mailboxName}
|
|
125
|
-
</p>
|
|
126
|
-
</div>
|
|
127
|
-
<Divider />
|
|
128
|
-
</>
|
|
129
|
-
)}
|
|
130
|
-
|
|
131
|
-
{/* Assigned Agent */}
|
|
132
|
-
{assignedAgent && (
|
|
133
|
-
<div className={`cm:flex ${size === 'small' ? 'cm:gap-1' : 'cm:gap-1.5'} cm:items-center cm:shrink-0`}>
|
|
134
|
-
<Avatar name={assignedAgent.name} size={size} />
|
|
135
|
-
<p className={`cm:font-normal cm:leading-5 ${sizeStyles.fontSize.body} cm:text-slate-text-subtle cm:whitespace-nowrap`}>
|
|
136
|
-
{assignedAgent.name}
|
|
137
|
-
</p>
|
|
138
|
-
</div>
|
|
139
|
-
)}
|
|
140
|
-
</div>
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
</div>
|
|
144
|
-
);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
export default ConversationBox;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export type ConversationType = 'chat' | 'email';
|
|
2
|
-
export type ConversationStatus = 'open' | 'pending' | 'closed';
|
|
3
|
-
export type ConversationBoxSize = 'default' | 'small';
|
|
4
|
-
|
|
5
|
-
export interface ConversationBoxProps {
|
|
6
|
-
type: ConversationType;
|
|
7
|
-
status: ConversationStatus;
|
|
8
|
-
size?: ConversationBoxSize;
|
|
9
|
-
senderName: string;
|
|
10
|
-
timestamp: string;
|
|
11
|
-
messagePreview: string;
|
|
12
|
-
heading?: string; // Only used for email type
|
|
13
|
-
mailboxName?: string;
|
|
14
|
-
assignedAgent?: {
|
|
15
|
-
name: string;
|
|
16
|
-
avatarColor?: string;
|
|
17
|
-
avatarInitial?: string;
|
|
18
|
-
};
|
|
19
|
-
onClick?: () => void;
|
|
20
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
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
|
-
|
|
@@ -1,31 +0,0 @@
|
|
|
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;
|
|
@@ -1,55 +0,0 @@
|
|
|
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;
|
|
@@ -1,93 +0,0 @@
|
|
|
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;
|