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,45 +0,0 @@
|
|
|
1
|
-
// Generate a deterministic hash from a string
|
|
2
|
-
export const generateStringHash = (str: string): number => {
|
|
3
|
-
let hash = 0;
|
|
4
|
-
for (let i = 0; i < str.length; i++) {
|
|
5
|
-
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
|
6
|
-
}
|
|
7
|
-
return hash;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
// Color options for labels
|
|
11
|
-
export const colorOptions = [
|
|
12
|
-
"#5398cf", // light blue
|
|
13
|
-
"#d04b4f", // red
|
|
14
|
-
"#ac80b7", // violet
|
|
15
|
-
"#8789c5", // purple
|
|
16
|
-
"#e3692c", // orange
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
// Generate a deterministic color for a label
|
|
20
|
-
export const getColorForLabel = (label: string | undefined): string => {
|
|
21
|
-
if (!label) return "#5398cf"; // light blue default
|
|
22
|
-
|
|
23
|
-
const hash = generateStringHash(label);
|
|
24
|
-
const index = Math.abs(hash) % colorOptions.length;
|
|
25
|
-
return colorOptions[index];
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Size configuration
|
|
29
|
-
export const sizeConfig = {
|
|
30
|
-
small: {
|
|
31
|
-
size: "cm:w-3.5 cm:h-3.5", // 14x14
|
|
32
|
-
text: "cm:text-[8px]",
|
|
33
|
-
leading: "cm:leading-[12px]",
|
|
34
|
-
},
|
|
35
|
-
default: {
|
|
36
|
-
size: "cm:w-4 cm:h-4", // 16x16
|
|
37
|
-
text: "cm:text-[9px]",
|
|
38
|
-
leading: "cm:leading-[13.5px]",
|
|
39
|
-
},
|
|
40
|
-
large: {
|
|
41
|
-
size: "cm:w-7 cm:h-[18px]", // 28x18
|
|
42
|
-
text: "cm:text-sm",
|
|
43
|
-
leading: "cm:leading-[18px]",
|
|
44
|
-
},
|
|
45
|
-
};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
2
|
-
import { getColorForLabel, sizeConfig } from './constants';
|
|
3
|
-
|
|
4
|
-
export interface AvatarProps {
|
|
5
|
-
name: string;
|
|
6
|
-
size?: 'small' | 'default' | 'large';
|
|
7
|
-
className?: string;
|
|
8
|
-
color?: string;
|
|
9
|
-
labelName?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const Avatar: React.FC<AvatarProps> = ({ name, size = 'default', className, color, labelName }) => {
|
|
13
|
-
// Get first letter of the name
|
|
14
|
-
const initial = name.charAt(0).toUpperCase() || 'A';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const assignedColor = useMemo(() =>
|
|
18
|
-
color ? color : getColorForLabel(labelName),
|
|
19
|
-
[color, labelName]);
|
|
20
|
-
|
|
21
|
-
const avatarData = useMemo(() => ({
|
|
22
|
-
backgroundColor: assignedColor,
|
|
23
|
-
}), [assignedColor]);
|
|
24
|
-
|
|
25
|
-
const sizeStyles = sizeConfig[size];
|
|
26
|
-
|
|
27
|
-
return (
|
|
28
|
-
<div
|
|
29
|
-
className={`cm:flex cm:flex-col cm:items-center cm:justify-center cm:rounded-full ${sizeStyles.size} cm:shrink-0 cm:relative ${className || ''}`}
|
|
30
|
-
style={avatarData}
|
|
31
|
-
>
|
|
32
|
-
<div className="cm:flex cm:flex-col cm:gap-0 cm:items-center cm:justify-center cm:w-full cm:h-full">
|
|
33
|
-
<p className={`cm:font-medium ${sizeStyles.leading} ${sizeStyles.text} cm:text-white cm:text-center cm:whitespace-nowrap`}>
|
|
34
|
-
{initial}
|
|
35
|
-
</p>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
export default Avatar;
|
|
42
|
-
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { Breadcrumb } from 'hiver-ui-kit-extended';
|
|
2
|
-
import { useBreadcrumb } from '../../hooks/useBreadcrumb';
|
|
3
|
-
|
|
4
|
-
const BreadcrumbsSection = () => {
|
|
5
|
-
|
|
6
|
-
const breadcrumbItems = useBreadcrumb();
|
|
7
|
-
console.log('breadcrumbItems', breadcrumbItems);
|
|
8
|
-
|
|
9
|
-
return (
|
|
10
|
-
<div className="cm:pb-2">
|
|
11
|
-
<Breadcrumb items={breadcrumbItems} />
|
|
12
|
-
</div>
|
|
13
|
-
);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export default BreadcrumbsSection;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import type { CardProps } from './types';
|
|
3
|
-
|
|
4
|
-
const Card: React.FC<CardProps> = ({
|
|
5
|
-
children,
|
|
6
|
-
header,
|
|
7
|
-
className = '',
|
|
8
|
-
onClick,
|
|
9
|
-
maxWidth = 'cm:max-w-[1000px]'
|
|
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} ${className}`}
|
|
14
|
-
onClick={onClick}
|
|
15
|
-
>
|
|
16
|
-
<div className="cm:flex cm:flex-col cm:items-start cm:overflow-clip cm:relative cm:rounded-[inherit] cm:w-full">
|
|
17
|
-
{header && (
|
|
18
|
-
<div className="cm:border-b cm:border-slate-border-light cm:box-border cm:flex cm:h-12 cm:items-center cm:px-0 cm:pr-4 cm:py-0 cm:relative cm:shrink-0 cm:w-full">
|
|
19
|
-
{header}
|
|
20
|
-
</div>
|
|
21
|
-
)}
|
|
22
|
-
<div className="cm:box-border cm:flex cm:flex-col cm:items-start cm:px-0 cm:py-0 cm:relative cm:shrink-0 cm:w-full">
|
|
23
|
-
{children}
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
</div>
|
|
27
|
-
);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export default Card;
|
|
31
|
-
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import RightPanel from "../RightPanel";
|
|
2
|
-
const Converation = () => {
|
|
3
|
-
return (
|
|
4
|
-
<div className="cm:flex cm:gap-4 cm:items-start cm:relative cm:shrink-0 cm:w-full cm:h-[100vh]">
|
|
5
|
-
<div className="cm:flex-1">dummy data</div>
|
|
6
|
-
|
|
7
|
-
<div className="cm:border-l cm:h-full cm:border-slate-border-light cm:w-[330px]">
|
|
8
|
-
<RightPanel />
|
|
9
|
-
</div>
|
|
10
|
-
</div>
|
|
11
|
-
);
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export default Converation;
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { useNavigate, useLocation } from 'react-router-dom';
|
|
3
|
-
import ConversationBox from '../ConversationBox';
|
|
4
|
-
import type { ConversationBoxProps } from '../ConversationBox/types';
|
|
5
|
-
|
|
6
|
-
// Sample conversation data for contact - can be filtered by contact later
|
|
7
|
-
const conversations: Omit<ConversationBoxProps, 'onClick'>[] = [
|
|
8
|
-
{
|
|
9
|
-
type: 'chat',
|
|
10
|
-
status: 'open',
|
|
11
|
-
senderName: 'Blake Lively',
|
|
12
|
-
timestamp: 'Feb 4, 02:45 PM',
|
|
13
|
-
messagePreview: 'I am unable to access key features. The loading times are excessive, hindering my workflow. Can you please investigate?',
|
|
14
|
-
mailboxName: 'Sales',
|
|
15
|
-
assignedAgent: {
|
|
16
|
-
name: 'Alice Adams',
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
type: 'email',
|
|
21
|
-
status: 'pending',
|
|
22
|
-
senderName: 'Ryan Reynolds',
|
|
23
|
-
timestamp: 'Feb 4, 02:45 PM',
|
|
24
|
-
heading: 'Issue with the app',
|
|
25
|
-
messagePreview: 'I am having trouble with the app. It keeps crashing and I am losing data. Can you please help?',
|
|
26
|
-
mailboxName: 'Finance',
|
|
27
|
-
assignedAgent: {
|
|
28
|
-
name: 'Andy Rue',
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
type: 'chat',
|
|
33
|
-
status: 'pending',
|
|
34
|
-
senderName: 'Chris Pratt',
|
|
35
|
-
timestamp: 'Feb 4, 02:45 PM',
|
|
36
|
-
messagePreview: 'I am encountering persistent error messages when trying to save changes. This is impacting my productivity. Please assist.',
|
|
37
|
-
mailboxName: 'Sales',
|
|
38
|
-
assignedAgent: {
|
|
39
|
-
name: 'Kate Wills',
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
type: 'email',
|
|
44
|
-
status: 'closed',
|
|
45
|
-
senderName: 'Zoe Saldana',
|
|
46
|
-
timestamp: 'Feb 4, 02:45 PM',
|
|
47
|
-
heading: "Can't log in to my account",
|
|
48
|
-
messagePreview: 'I am experiencing synchronization problems across devices. My data is not updating correctly. Could you resolve this?',
|
|
49
|
-
mailboxName: 'Finance',
|
|
50
|
-
assignedAgent: {
|
|
51
|
-
name: 'Kevin Smith',
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
];
|
|
55
|
-
|
|
56
|
-
const ContactConversation: React.FC = () => {
|
|
57
|
-
const navigate = useNavigate();
|
|
58
|
-
const location = useLocation();
|
|
59
|
-
|
|
60
|
-
console.log('Current route:', location.pathname);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const handleConversationClick = () => {
|
|
64
|
-
navigate('/conversation');
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<div className="cm:flex cm:flex-col cm:gap-2 cm:p-6 cm:w-full">
|
|
69
|
-
{conversations.map((conversation, index) => (
|
|
70
|
-
<ConversationBox
|
|
71
|
-
key={index}
|
|
72
|
-
{...conversation}
|
|
73
|
-
onClick={handleConversationClick}
|
|
74
|
-
/>
|
|
75
|
-
))}
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
export default ContactConversation;
|
|
81
|
-
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import React, { useMemo } from 'react';
|
|
2
|
-
import { useParams } from 'react-router-dom';
|
|
3
|
-
import { contactsData, accountData } from '../../constants/index';
|
|
4
|
-
import accountsIcon from '../../assets/accounts.svg';
|
|
5
|
-
import atTheRateIcon from '../../assets/at_the_rate.svg';
|
|
6
|
-
import phoneIcon from '../../assets/phone.svg';
|
|
7
|
-
import Avatar from '../Avatar';
|
|
8
|
-
import EditContact from '../Contacts/EditContact';
|
|
9
|
-
|
|
10
|
-
interface DetailRowProps {
|
|
11
|
-
icon: React.ReactNode;
|
|
12
|
-
label: string;
|
|
13
|
-
value: string | number;
|
|
14
|
-
valueColor?: 'default' | 'primary';
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const DetailRow: React.FC<DetailRowProps> = ({ icon, label, value, valueColor = 'default' }) => (
|
|
18
|
-
<div className="cm:flex cm:flex-col cm:items-start cm:relative cm:shrink-0 cm:w-full">
|
|
19
|
-
<div className="cm:flex cm:h-9 cm:items-center cm:relative cm:shrink-0 cm:w-full">
|
|
20
|
-
<div className="cm:flex cm:gap-2 cm:items-center cm:relative cm:shrink-0 cm:w-[100px]">
|
|
21
|
-
<div className="cm:relative cm:shrink-0 cm:w-3.5 cm:h-3.5 cm:flex cm:items-center cm:justify-center">
|
|
22
|
-
{icon}
|
|
23
|
-
</div>
|
|
24
|
-
<p className="cm:font-normal cm:leading-5 cm:text-sm cm:text-left cm:text-slate-text-subtle cm:w-[90px]">
|
|
25
|
-
{label}
|
|
26
|
-
</p>
|
|
27
|
-
</div>
|
|
28
|
-
<div className="cm:flex-1 cm:flex cm:gap-2 cm:h-full cm:items-center cm:min-w-0 cm:px-1 cm:py-0 cm:relative cm:shrink-0">
|
|
29
|
-
<p className={`cm:font-medium cm:leading-5 cm:text-sm cm:whitespace-nowrap ${
|
|
30
|
-
valueColor === 'primary'
|
|
31
|
-
? 'cm:text-primary-text-default'
|
|
32
|
-
: 'cm:text-slate-text-title'
|
|
33
|
-
}`}>
|
|
34
|
-
{value}
|
|
35
|
-
</p>
|
|
36
|
-
</div>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const ContactDetails: React.FC = () => {
|
|
42
|
-
const { contactId } = useParams<{ contactId: string }>();
|
|
43
|
-
|
|
44
|
-
const contact = useMemo(() => {
|
|
45
|
-
if (!contactId) return null;
|
|
46
|
-
return contactsData.data.results.find(
|
|
47
|
-
(cont: { id: number }) => cont.id.toString() === contactId
|
|
48
|
-
);
|
|
49
|
-
}, [contactId]);
|
|
50
|
-
|
|
51
|
-
const account = useMemo(() => {
|
|
52
|
-
if (!contact?.company?.id) return null;
|
|
53
|
-
return accountData.data.results.find(
|
|
54
|
-
(acc: { id: number }) => acc.id === contact.company.id
|
|
55
|
-
);
|
|
56
|
-
}, [contact]);
|
|
57
|
-
|
|
58
|
-
if (!contact) {
|
|
59
|
-
return (
|
|
60
|
-
<div>
|
|
61
|
-
<p className="cm:text-sm cm:text-slate-text-subtle">Contact not found</p>
|
|
62
|
-
</div>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return (
|
|
67
|
-
<div className="cm:flex cm:flex-col cm:gap-[20px] cm:items-stretch cm:relative cm:shrink-0 cm:w-full">
|
|
68
|
-
{/* Contact Name Section */}
|
|
69
|
-
<div className="cm:flex cm:items-center cm:justify-between cm:px-0 cm:rounded-[6px] cm:shrink-0">
|
|
70
|
-
<div className="cm:flex cm:gap-3 cm:items-center cm:relative cm:shrink-0 cm:w-full">
|
|
71
|
-
{/* Contact Avatar */}
|
|
72
|
-
<Avatar
|
|
73
|
-
name={contact.name}
|
|
74
|
-
labelName={contact.name}
|
|
75
|
-
size="large"
|
|
76
|
-
className="cm:w-[28px] cm:h-[28px]"
|
|
77
|
-
/>
|
|
78
|
-
<p className="cm:font-medium cm:leading-5 cm:text-sm cm:text-slate-text-title cm:whitespace-nowrap">
|
|
79
|
-
{contact.name}
|
|
80
|
-
</p>
|
|
81
|
-
</div>
|
|
82
|
-
<EditContact />
|
|
83
|
-
</div>
|
|
84
|
-
|
|
85
|
-
{/* Details Section */}
|
|
86
|
-
{/* <div className="cm:flex cm:flex-col cm:gap-0.5 cm:items-start cm:relative cm:shrink-0 cm:w-full"> */}
|
|
87
|
-
{account && (
|
|
88
|
-
<DetailRow
|
|
89
|
-
icon={<img src={accountsIcon} alt="account" className="cm:w-3.5 cm:h-3.5" />}
|
|
90
|
-
label="Account"
|
|
91
|
-
value={account.name}
|
|
92
|
-
/>
|
|
93
|
-
)}
|
|
94
|
-
<DetailRow
|
|
95
|
-
icon={<img src={atTheRateIcon} alt="email" className="cm:w-3.5 cm:h-3.5" />}
|
|
96
|
-
label="Email"
|
|
97
|
-
value={contact.email || 'N/A'}
|
|
98
|
-
valueColor="primary"
|
|
99
|
-
/>
|
|
100
|
-
<DetailRow
|
|
101
|
-
icon={<img src={phoneIcon} alt="phone" className="cm:w-3.5 cm:h-3.5" />}
|
|
102
|
-
label="Phone"
|
|
103
|
-
value={contact.phone_number || 'N/A'}
|
|
104
|
-
/>
|
|
105
|
-
{/* </div> */}
|
|
106
|
-
</div>
|
|
107
|
-
);
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
export default ContactDetails;
|
|
111
|
-
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { Dialog, Button, TextField } from "hiver-ui-kit-extended";
|
|
3
|
-
import Avatar from "../Avatar";
|
|
4
|
-
import closeIcon from "../../assets/close.svg";
|
|
5
|
-
import Icon from "../Icon";
|
|
6
|
-
|
|
7
|
-
interface Contact {
|
|
8
|
-
id: string;
|
|
9
|
-
name: string;
|
|
10
|
-
email: string;
|
|
11
|
-
phone_number?: string;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface EditContactProps {
|
|
15
|
-
contact?: Contact;
|
|
16
|
-
open?: boolean;
|
|
17
|
-
onClose?: () => void;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const EditContact = ({ contact, open = false, onClose }: EditContactProps) => {
|
|
21
|
-
const [dialogOpen, setDialogOpen] = useState(open);
|
|
22
|
-
const [formData, setFormData] = useState({
|
|
23
|
-
name: contact?.name || "",
|
|
24
|
-
email: contact?.email || "",
|
|
25
|
-
phone_number: contact?.phone_number || "",
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const [errors, setErrors] = useState({
|
|
29
|
-
name: "",
|
|
30
|
-
email: "",
|
|
31
|
-
phone_number: "",
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
const handleClick = () => {
|
|
35
|
-
setDialogOpen(true);
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const handleClose = () => {
|
|
39
|
-
setDialogOpen(false);
|
|
40
|
-
setFormData({
|
|
41
|
-
name: contact?.name || "",
|
|
42
|
-
email: contact?.email || "",
|
|
43
|
-
phone_number: contact?.phone_number || "",
|
|
44
|
-
});
|
|
45
|
-
setErrors({ name: "", email: "", phone_number: "" });
|
|
46
|
-
onClose?.();
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const validateForm = () => {
|
|
50
|
-
const newErrors = {
|
|
51
|
-
name: "",
|
|
52
|
-
email: "",
|
|
53
|
-
phone_number: "",
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
let isValid = true;
|
|
57
|
-
|
|
58
|
-
if (!formData.name.trim()) {
|
|
59
|
-
newErrors.name = "Name is required";
|
|
60
|
-
isValid = false;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!formData.email.trim()) {
|
|
64
|
-
newErrors.email = "Email is required";
|
|
65
|
-
isValid = false;
|
|
66
|
-
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
|
67
|
-
newErrors.email = "Please enter a valid email";
|
|
68
|
-
isValid = false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
setErrors(newErrors);
|
|
72
|
-
return isValid;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const handleSubmit = () => {
|
|
76
|
-
if (validateForm()) {
|
|
77
|
-
console.log("Form submitted:", formData);
|
|
78
|
-
alert("Contact updated successfully!");
|
|
79
|
-
handleClose();
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const handleInputChange = (field: keyof typeof formData, value: string) => {
|
|
84
|
-
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
85
|
-
// Clear error when user starts typing
|
|
86
|
-
if (errors[field]) {
|
|
87
|
-
setErrors((prev) => ({ ...prev, [field]: "" }));
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
const renderHeader = () => {
|
|
92
|
-
return (
|
|
93
|
-
<div className="cm:flex cm:items-center cm:justify-between cm:w-full">
|
|
94
|
-
<div className="cm:text-slate-text-title cm:text-base cm:font-semibold cm:leading-6">
|
|
95
|
-
{contact ? "Edit Contact" : "Add Contact"}
|
|
96
|
-
</div>
|
|
97
|
-
<Button
|
|
98
|
-
onClick={handleClose}
|
|
99
|
-
variant="text"
|
|
100
|
-
icon={closeIcon}
|
|
101
|
-
size="small"
|
|
102
|
-
color="primary"
|
|
103
|
-
className="cm:p-0"
|
|
104
|
-
/>
|
|
105
|
-
</div>
|
|
106
|
-
);
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const renderContent = () => {
|
|
110
|
-
return (
|
|
111
|
-
<div className="" onClick={(e) => e.stopPropagation()}>
|
|
112
|
-
{/* Contact Avatar and Info */}
|
|
113
|
-
{contact && (
|
|
114
|
-
<div className="cm:flex cm:items-center cm:gap-3 cm:mb-6">
|
|
115
|
-
<Avatar name={formData.name} labelName={formData.name} />
|
|
116
|
-
<div className="cm:flex cm:flex-col">
|
|
117
|
-
<p className="cm:text-slate-text-title cm:text-sm cm:font-medium cm:leading-5 cm:m-0">
|
|
118
|
-
{contact.name}
|
|
119
|
-
</p>
|
|
120
|
-
<p className="cm:text-slate-text-subtle cm:text-xs cm:font-normal cm:leading-[18px] cm:m-0">
|
|
121
|
-
{contact.email}
|
|
122
|
-
</p>
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
|
|
127
|
-
{/* Form Fields */}
|
|
128
|
-
<div className="cm:flex cm:flex-col cm:gap-4 cm:mb-6">
|
|
129
|
-
{/* Name Field */}
|
|
130
|
-
<div className="cm:flex cm:flex-col cm:gap-1.5">
|
|
131
|
-
<label className="cm:text-slate-text-title cm:text-sm cm:font-medium cm:leading-5">
|
|
132
|
-
Name <span className="cm:text-error">*</span>
|
|
133
|
-
</label>
|
|
134
|
-
<TextField
|
|
135
|
-
placeholder="Enter contact name"
|
|
136
|
-
value={formData.name}
|
|
137
|
-
onChange={(e) => handleInputChange("name", e.target.value)}
|
|
138
|
-
className="cm:w-full"
|
|
139
|
-
error={!!errors.name}
|
|
140
|
-
/>
|
|
141
|
-
{errors.name && (
|
|
142
|
-
<span className="cm:text-error cm:text-xs cm:font-normal cm:leading-[18px]">
|
|
143
|
-
{errors.name}
|
|
144
|
-
</span>
|
|
145
|
-
)}
|
|
146
|
-
</div>
|
|
147
|
-
|
|
148
|
-
{/* Email Field */}
|
|
149
|
-
<div className="cm:flex cm:flex-col cm:gap-1.5">
|
|
150
|
-
<label className="cm:text-slate-text-title cm:text-sm cm:font-medium cm:leading-5">
|
|
151
|
-
Email <span className="cm:text-error">*</span>
|
|
152
|
-
</label>
|
|
153
|
-
<TextField
|
|
154
|
-
placeholder="Enter email address"
|
|
155
|
-
value={formData.email}
|
|
156
|
-
onChange={(e) => handleInputChange("email", e.target.value)}
|
|
157
|
-
className="cm:w-full"
|
|
158
|
-
error={!!errors.email}
|
|
159
|
-
/>
|
|
160
|
-
{errors.email && (
|
|
161
|
-
<span className="cm:text-error cm:text-xs cm:font-normal cm:leading-[18px]">
|
|
162
|
-
{errors.email}
|
|
163
|
-
</span>
|
|
164
|
-
)}
|
|
165
|
-
</div>
|
|
166
|
-
|
|
167
|
-
{/* Phone Field */}
|
|
168
|
-
<div className="cm:flex cm:flex-col cm:gap-1.5">
|
|
169
|
-
<label className="cm:text-slate-text-title cm:text-sm cm:font-medium cm:leading-5">
|
|
170
|
-
Phone Number
|
|
171
|
-
</label>
|
|
172
|
-
<TextField
|
|
173
|
-
placeholder="Enter phone number"
|
|
174
|
-
value={formData.phone_number}
|
|
175
|
-
onChange={(e) => handleInputChange("phone_number", e.target.value)}
|
|
176
|
-
className="cm:w-full"
|
|
177
|
-
/>
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
|
|
181
|
-
{/* Action Buttons */}
|
|
182
|
-
|
|
183
|
-
</div>
|
|
184
|
-
);
|
|
185
|
-
};
|
|
186
|
-
|
|
187
|
-
return (
|
|
188
|
-
<div>
|
|
189
|
-
|
|
190
|
-
<Icon name="edit" size={16} type="active" color="#0F172A" onClick={handleClick}/>
|
|
191
|
-
|
|
192
|
-
<Dialog
|
|
193
|
-
open={dialogOpen}
|
|
194
|
-
onClose={handleClose}
|
|
195
|
-
maxWidth="sm"
|
|
196
|
-
title={renderHeader()}
|
|
197
|
-
className="cm:bg-slate-surface-white cm:w-[400px]"
|
|
198
|
-
footer={ <div className="cm:flex cm:gap-3 cm:items-center cm:justify-end cm:w-full">
|
|
199
|
-
<Button variant="outlined" size="medium" onClick={handleClose}>
|
|
200
|
-
Cancel
|
|
201
|
-
</Button>
|
|
202
|
-
<Button onClick={handleSubmit} size="medium">
|
|
203
|
-
Save
|
|
204
|
-
</Button>
|
|
205
|
-
</div>}
|
|
206
|
-
>
|
|
207
|
-
{renderContent()}
|
|
208
|
-
</Dialog>
|
|
209
|
-
</div>
|
|
210
|
-
);
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
export default EditContact;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
export const mockContacts = [
|
|
2
|
-
{
|
|
3
|
-
id: '1',
|
|
4
|
-
name: 'Bob Johnson',
|
|
5
|
-
email: 'bobjohnson@company.com',
|
|
6
|
-
initial: 'B',
|
|
7
|
-
avatarColor: 'bg-[#ac80b7]', // pastel-violet
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
id: '2',
|
|
11
|
-
name: 'Martin George',
|
|
12
|
-
email: 'martingeorge@company.com',
|
|
13
|
-
initial: 'M',
|
|
14
|
-
avatarColor: 'bg-[#d04b4f]', // pastel-red
|
|
15
|
-
isSelected: true,
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
id: '3',
|
|
19
|
-
name: 'Alice Smith',
|
|
20
|
-
email: 'alicesmith@company.com',
|
|
21
|
-
initial: 'A',
|
|
22
|
-
avatarColor: 'bg-[#5398cf]', // pastel-light-blue
|
|
23
|
-
},
|
|
24
|
-
];
|