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.
Files changed (89) hide show
  1. package/dist/customer-module.css +1 -0
  2. package/dist/customer-module.js +29033 -0
  3. package/dist/customer-module.js.map +1 -0
  4. package/package.json +21 -2
  5. package/.cursor/rules/context.md +0 -306
  6. package/.cursor/rules/guardrails.md +0 -35
  7. package/.env +0 -1
  8. package/.github/workflows/publish-to-npm-beta.yml +0 -51
  9. package/.github/workflows/publish-to-npm.yml +0 -58
  10. package/eslint.config.js +0 -23
  11. package/index.html +0 -13
  12. package/postcss-unwrap-layers.js +0 -31
  13. package/postcss.config.js +0 -11
  14. package/src/App.css +0 -40
  15. package/src/App.tsx +0 -58
  16. package/src/assets/accounts.svg +0 -3
  17. package/src/assets/at_the_rate.svg +0 -10
  18. package/src/assets/buildings.svg +0 -3
  19. package/src/assets/chat.svg +0 -3
  20. package/src/assets/close.svg +0 -3
  21. package/src/assets/contacts.svg +0 -3
  22. package/src/assets/conversation.svg +0 -10
  23. package/src/assets/customers.svg +0 -10
  24. package/src/assets/details.svg +0 -3
  25. package/src/assets/domain.svg +0 -10
  26. package/src/assets/edit.svg +0 -15
  27. package/src/assets/email.svg +0 -3
  28. package/src/assets/google.svg +0 -8
  29. package/src/assets/inbox.svg +0 -0
  30. package/src/assets/message.svg +0 -3
  31. package/src/assets/no-data.svg +0 -9
  32. package/src/assets/open_in_a_new_tab.svg +0 -10
  33. package/src/assets/phone.svg +0 -3
  34. package/src/assets/react.svg +0 -1
  35. package/src/assets/search.svg +0 -3
  36. package/src/assets/search_typing.svg +0 -4
  37. package/src/assets/sm_contacts.svg +0 -3
  38. package/src/assets/sm_inbox.svg +0 -3
  39. package/src/assets/sm_slider.svg +0 -3
  40. package/src/assets/status-resolved.svg +0 -3
  41. package/src/assets/status-snoozed.svg +0 -4
  42. package/src/assets/status_open.svg +0 -1
  43. package/src/components/AccountContacts/index.tsx +0 -107
  44. package/src/components/AccountDetails/index.tsx +0 -102
  45. package/src/components/AccountsConversation/index.tsx +0 -75
  46. package/src/components/Avatar/constants.tsx +0 -45
  47. package/src/components/Avatar/index.tsx +0 -42
  48. package/src/components/BreadcrumbsSection/index.tsx +0 -16
  49. package/src/components/Card/index.tsx +0 -31
  50. package/src/components/Card/types.ts +0 -10
  51. package/src/components/ContactConversation/Converation.tsx +0 -14
  52. package/src/components/ContactConversation/index.tsx +0 -81
  53. package/src/components/ContactDetails/index.tsx +0 -111
  54. package/src/components/Contacts/EditContact.tsx +0 -213
  55. package/src/components/Contacts/constants/index.tsx +0 -24
  56. package/src/components/Contacts/index.tsx +0 -171
  57. package/src/components/ConversationBox/constants.tsx +0 -99
  58. package/src/components/ConversationBox/index.tsx +0 -147
  59. package/src/components/ConversationBox/types.ts +0 -20
  60. package/src/components/CustomersLayout/index.tsx +0 -20
  61. package/src/components/DetailsCard/index.tsx +0 -31
  62. package/src/components/DetailsCard/types.ts +0 -10
  63. package/src/components/EmptyData/NoDataFound.tsx +0 -31
  64. package/src/components/Header/index.tsx +0 -55
  65. package/src/components/Icon/index.tsx +0 -93
  66. package/src/components/Icon/types.ts +0 -13
  67. package/src/components/Listing/AccountTable.tsx +0 -47
  68. package/src/components/Listing/ContactTable.tsx +0 -76
  69. package/src/components/RightPanel/AccountPanel.tsx +0 -123
  70. package/src/components/RightPanel/ContactPanel.tsx +0 -142
  71. package/src/components/RightPanel/index.tsx +0 -167
  72. package/src/components/Search/SearchDialog.tsx +0 -150
  73. package/src/components/TabsSection/index.tsx +0 -49
  74. package/src/constants/index.tsx +0 -645
  75. package/src/hooks/useBreadcrumb.tsx +0 -93
  76. package/src/index.css +0 -315
  77. package/src/main.tsx +0 -14
  78. package/src/pages/AccountDetail.tsx +0 -68
  79. package/src/pages/Accounts.tsx +0 -12
  80. package/src/pages/ContactDetail.tsx +0 -55
  81. package/src/pages/Contacts.tsx +0 -12
  82. package/src/stores/count.tsx +0 -17
  83. package/src/types/index.ts +0 -0
  84. package/tailwind.config.js +0 -179
  85. package/tsconfig.app.json +0 -36
  86. package/tsconfig.json +0 -7
  87. package/tsconfig.node.json +0 -26
  88. package/vite.config.ts +0 -31
  89. /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,10 +0,0 @@
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
-
@@ -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;