livechat-assistant 0.0.8 → 0.0.9
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/ng-package.json +10 -0
- package/package.json +19 -23
- package/src/lib/components/chat-history/chat-history.component.html +83 -0
- package/src/lib/components/chat-history/chat-history.component.scss +0 -0
- package/src/lib/components/chat-history/chat-history.component.spec.ts +23 -0
- package/src/lib/components/chat-history/chat-history.component.ts +97 -0
- package/src/lib/components/chat-widget/chat-widget.component.html +87 -0
- package/src/lib/components/chat-widget/chat-widget.component.scss +77 -0
- package/src/lib/components/chat-widget/chat-widget.component.spec.ts +23 -0
- package/src/lib/components/chat-widget/chat-widget.component.ts +84 -0
- package/src/lib/components/help-support/help-support.component.html +106 -0
- package/src/lib/components/help-support/help-support.component.scss +0 -0
- package/src/lib/components/help-support/help-support.component.spec.ts +23 -0
- package/src/lib/components/help-support/help-support.component.ts +46 -0
- package/src/lib/components/home/home.component.html +64 -0
- package/src/lib/components/home/home.component.scss +0 -0
- package/src/lib/components/home/home.component.spec.ts +23 -0
- package/src/lib/components/home/home.component.ts +18 -0
- package/src/lib/components/index.ts +2 -0
- package/src/lib/components/message/message.component.html +119 -0
- package/src/lib/components/message/message.component.scss +0 -0
- package/src/lib/components/message/message.component.spec.ts +23 -0
- package/src/lib/components/message/message.component.ts +48 -0
- package/src/lib/components/success-message/success-message.component.html +33 -0
- package/src/lib/components/success-message/success-message.component.scss +65 -0
- package/src/lib/components/success-message/success-message.component.spec.ts +23 -0
- package/src/lib/components/success-message/success-message.component.ts +34 -0
- package/src/lib/components/support-buttons/support-buttons.component.html +42 -0
- package/src/lib/components/support-buttons/support-buttons.component.scss +49 -0
- package/src/lib/components/support-buttons/support-buttons.component.spec.ts +21 -0
- package/src/lib/components/support-buttons/support-buttons.component.ts +33 -0
- package/src/lib/environment/environment.test.ts +15 -0
- package/src/lib/livechat-assistant.component.spec.ts +23 -0
- package/src/lib/livechat-assistant.component.ts +15 -0
- package/src/lib/livechat-assistant.service.spec.ts +16 -0
- package/src/lib/livechat-assistant.service.ts +9 -0
- package/src/lib/services/index.ts +1 -0
- package/src/lib/services/request.ts +58 -0
- package/src/lib/utilities/helper.ts +23 -0
- package/src/lib/utilities/index.ts +1 -0
- package/src/public-api.ts +10 -0
- package/src/styles.css +5 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +15 -0
- package/fesm2022/livechat-assistant.mjs +0 -445
- package/fesm2022/livechat-assistant.mjs.map +0 -1
- package/index.d.ts +0 -5
- package/lib/components/chat-history/chat-history.component.d.ts +0 -94
- package/lib/components/chat-widget/chat-widget.component.d.ts +0 -17
- package/lib/components/help-support/help-support.component.d.ts +0 -15
- package/lib/components/home/home.component.d.ts +0 -8
- package/lib/components/index.d.ts +0 -2
- package/lib/components/message/message.component.d.ts +0 -16
- package/lib/components/success-message/success-message.component.d.ts +0 -15
- package/lib/components/support-buttons/support-buttons.component.d.ts +0 -12
- package/lib/environment/environment.test.d.ts +0 -15
- package/lib/livechat-assistant.component.d.ts +0 -5
- package/lib/livechat-assistant.service.d.ts +0 -6
- package/lib/services/index.d.ts +0 -1
- package/lib/services/request.d.ts +0 -12
- package/lib/utilities/helper.d.ts +0 -9
- package/lib/utilities/index.d.ts +0 -1
- package/public-api.d.ts +0 -5
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "livechat-assistant",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
"default": "./fesm2022/livechat-assistant.mjs"
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "livechat-assistant",
|
|
3
|
+
"version": "0.0.9",
|
|
4
|
+
"description": "Angular AI live chat widget component",
|
|
5
|
+
"author": "sleekpenny",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"module": "fesm2022/ai-livechat-assistants.mjs",
|
|
9
|
+
"types": "index.d.ts",
|
|
10
|
+
"peerDependencies": {
|
|
11
|
+
"@angular/common": "^19.2.0",
|
|
12
|
+
"@angular/core": "^19.2.0"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"preline": "^4.0.1",
|
|
16
|
+
"tslib": "^2.3.0"
|
|
17
|
+
},
|
|
18
|
+
"sideEffects": false
|
|
19
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<div class="max-h-[600px] rounded-xl w-full flex flex-col justify-center items-center">
|
|
2
|
+
<div
|
|
3
|
+
class="flex items-center justify-between px-4 py-5 dark:bg-neutral-700/20 shadow-xl bg-neutral-50 hover:bg-neutral-800/50 rounded-xl w-full">
|
|
4
|
+
<h3 class="font-semibold text-neutral-800 dark:text-neutral-200 fs-16">Messages</h3>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<!-- <div class="flex gap-2">
|
|
8
|
+
<button (click)="navigateToTab(2)" class="mt-4 mb-2 bg-red-600 text-white rounded-md hover:bg-blue-700 transition w-fit">
|
|
9
|
+
<p class="text-center px-4 py-1.5 text-sm m-0">Ask AI question</p>
|
|
10
|
+
</button>
|
|
11
|
+
<button (click)="navigateToTab(2)" class="mt-4 mb-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition w-fit">
|
|
12
|
+
<p class="text-center px-4 py-1.5 text-sm m-0">Chat with Live Agent</p>
|
|
13
|
+
</button>
|
|
14
|
+
</div> -->
|
|
15
|
+
|
|
16
|
+
<div class="flex-1 overflow-y-auto hideScroll">
|
|
17
|
+
@for (conversation of conversations; track $index) {
|
|
18
|
+
|
|
19
|
+
<div (click)="selectConversation(conversation.id, 2)"
|
|
20
|
+
class="flex items-center gap-3 px-4 py-3 hover:bg-neutral-800 cursor-pointer transition-colors">
|
|
21
|
+
|
|
22
|
+
<!-- Avatar -->
|
|
23
|
+
<div class="relative flex-shrink-0">
|
|
24
|
+
@if (conversation.avatar) {
|
|
25
|
+
<div [class]="'w-12 h-12 flex items-center font-semibold justify-center rounded-full overflow-hidden' + conversation.color">
|
|
26
|
+
<h1 class="text-white text-xl">AI</h1>
|
|
27
|
+
</div>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@if (!conversation.avatar && conversation.initials) {
|
|
31
|
+
<div [class]="'w-12 h-12 rounded-full flex items-center justify-center text-white font-semibold text-xl' + (conversation.color || 'bg-gray-500')">
|
|
32
|
+
{{ conversation.initials }}
|
|
33
|
+
</div>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@if (conversation.online) {
|
|
37
|
+
<span class="absolute bottom-0 right-0 w-3 h-3 bg-green-500 border-2 border-white rounded-full"></span>
|
|
38
|
+
}
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<div class="flex-1 min-w-0">
|
|
42
|
+
<div class="flex items-center justify-between mb-1">
|
|
43
|
+
<h3 class="font-semibold text-neutral-800 dark:text-white fs-16 truncate">{{ conversation.name }}</h3>
|
|
44
|
+
<span class="text-neutral-800 dark:text-neutral-400 text-xs">Date </span>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="flex items-center justify-between">
|
|
47
|
+
<p class="text-sm text-neutral-400 truncate flex items-center gap-1">
|
|
48
|
+
@if (conversation.hasAttachment) {
|
|
49
|
+
<span class="text-neutral-400">📎</span>
|
|
50
|
+
}
|
|
51
|
+
{{ conversation.lastMessage }}
|
|
52
|
+
</p>
|
|
53
|
+
<div class="flex items-center gap-2 flex-shrink-0 ml-2">
|
|
54
|
+
|
|
55
|
+
@if (conversation.status === 'read') {
|
|
56
|
+
<svg class="w-4 h-4 text-green-400" fill="currentColor"
|
|
57
|
+
viewBox="0 0 24 24">
|
|
58
|
+
<path
|
|
59
|
+
d="M18 7l-1.41-1.41-6.34 6.34 1.41 1.41L18 7zm4.24-1.41L11.66 16.17 7.48 12l-1.41 1.41L11.66 19l12-12-1.42-1.41zM.41 13.41L6 19l1.41-1.41L1.83 12 .41 13.41z" />
|
|
60
|
+
</svg>
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@if (conversation.status === 'delivered') {
|
|
64
|
+
<svg class="w-4 h-4 text-blue-600" fill="currentColor"
|
|
65
|
+
viewBox="0 0 24 24">
|
|
66
|
+
<path
|
|
67
|
+
d="M18 7l-1.41-1.41-6.34 6.34 1.41 1.41L18 7zm4.24-1.41L11.66 16.17 7.48 12l-1.41 1.41L11.66 19l12-12-1.42-1.41zM.41 13.41L6 19l1.41-1.41L1.83 12 .41 13.41z" />
|
|
68
|
+
</svg>
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@if (conversation.unreadCount) {
|
|
72
|
+
<span
|
|
73
|
+
class="bg-blue-600 text-white text-xs font-semibold rounded-full w-5 h-5 flex items-center justify-center">
|
|
74
|
+
{{ conversation.unreadCount }}
|
|
75
|
+
</span>
|
|
76
|
+
}
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
}
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { ChatHistoryComponent } from './chat-history.component';
|
|
4
|
+
|
|
5
|
+
describe('ChatHistoryComponent', () => {
|
|
6
|
+
let component: ChatHistoryComponent;
|
|
7
|
+
let fixture: ComponentFixture<ChatHistoryComponent>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [ChatHistoryComponent]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(ChatHistoryComponent);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Component, ChangeDetectionStrategy, EventEmitter, Output, } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
@Component({
|
|
4
|
+
selector: 'app-chat-history',
|
|
5
|
+
imports: [],
|
|
6
|
+
templateUrl: './chat-history.component.html',
|
|
7
|
+
styleUrl: './chat-history.component.scss',
|
|
8
|
+
standalone: true,
|
|
9
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
10
|
+
})
|
|
11
|
+
export class ChatHistoryComponent {
|
|
12
|
+
@Output() navigate= new EventEmitter<number>();
|
|
13
|
+
|
|
14
|
+
conversations = [
|
|
15
|
+
{
|
|
16
|
+
id: 1,
|
|
17
|
+
name: 'Costa Quinn',
|
|
18
|
+
avatar: true,
|
|
19
|
+
lastMessage: 'Yes, you can!',
|
|
20
|
+
time: '11M',
|
|
21
|
+
status: 'read',
|
|
22
|
+
color: 'bg-red-600',
|
|
23
|
+
online: true,
|
|
24
|
+
initials: ''
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 2,
|
|
28
|
+
name: 'Rachel Doe',
|
|
29
|
+
initials: 'R',
|
|
30
|
+
color: 'bg-blue-600',
|
|
31
|
+
lastMessage: 'When using open method, const select = n...',
|
|
32
|
+
time: '14M',
|
|
33
|
+
unreadCount: 1,
|
|
34
|
+
online: false,
|
|
35
|
+
avatar:false
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 3,
|
|
39
|
+
name: 'Lewis Clarke',
|
|
40
|
+
avatar: true,
|
|
41
|
+
lastMessage: 'Have a great all free! 😊',
|
|
42
|
+
time: '15M',
|
|
43
|
+
online: true,
|
|
44
|
+
status: 'read',
|
|
45
|
+
color: 'bg-blue-400'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 4,
|
|
49
|
+
name: 'Technical issues',
|
|
50
|
+
initials: 'T',
|
|
51
|
+
color: 'bg-orange-500',
|
|
52
|
+
lastMessage: 'Great!',
|
|
53
|
+
time: '35M',
|
|
54
|
+
status: 'read',
|
|
55
|
+
hasAttachment: true,
|
|
56
|
+
online:false
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 5,
|
|
60
|
+
name: 'Bob Dean',
|
|
61
|
+
initials: 'B',
|
|
62
|
+
color: 'bg-pink-500',
|
|
63
|
+
lastMessage: 'Hey Preline team, I got an p3p48 while using...',
|
|
64
|
+
time: '1H',
|
|
65
|
+
unreadCount: 1,
|
|
66
|
+
online:true
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 6,
|
|
70
|
+
name: 'Mark Colbert',
|
|
71
|
+
initials: 'M',
|
|
72
|
+
color: 'bg-teal-500',
|
|
73
|
+
lastMessage: 'Voice message',
|
|
74
|
+
time: '6DM',
|
|
75
|
+
status: 'delivered',
|
|
76
|
+
online: false
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 7,
|
|
80
|
+
name: 'Ella Lauda',
|
|
81
|
+
avatar: true,
|
|
82
|
+
lastMessage: 'I am really impressed! Can\'t wait...',
|
|
83
|
+
time: '37M',
|
|
84
|
+
online: false
|
|
85
|
+
}
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
selectConversation(id: number, tabIndex:number) {
|
|
89
|
+
console.log('Selected conversation:', id);
|
|
90
|
+
this.navigate.emit(tabIndex);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
navigateToTab(tabIndex:number) {
|
|
94
|
+
this.navigate.emit(tabIndex);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<div class="chatbot bg-white dark:bg-neutral-900 ">
|
|
2
|
+
|
|
3
|
+
<div>
|
|
4
|
+
@if (activeTab === 1) {
|
|
5
|
+
<div role="tabpanel">
|
|
6
|
+
<app-home (navigate)="navigateToTab($event)" />
|
|
7
|
+
</div>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@if (activeTab === 2) {
|
|
11
|
+
<div role="tabpanel">
|
|
12
|
+
<app-message [messages]="messages" [isTyping]="isTyping" (sendMessage)="sendMessage($event)" (navigate)="navigateToTab($event)"/>
|
|
13
|
+
</div>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@if (activeTab === 3) {
|
|
17
|
+
<div role="tabpanel">
|
|
18
|
+
<app-help-support (navigate)="navigateToTab($event)"/>
|
|
19
|
+
</div>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@if (activeTab === 4) {
|
|
23
|
+
<div role="tabpanel">
|
|
24
|
+
<app-chat-history (navigate)="navigateToTab($event)"/>
|
|
25
|
+
</div>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@if (activeTab === 5) {
|
|
29
|
+
<div role="tabpanel">
|
|
30
|
+
<app-success-message (navigate)="navigateToTab($event)" />
|
|
31
|
+
</div>
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="">
|
|
37
|
+
<nav class="flex justify-evenly" aria-label="Tabs" role="tablist">
|
|
38
|
+
<button type="button" (click)="setActiveTab(1)"
|
|
39
|
+
[class.text-blue-500]="activeTab === 1"
|
|
40
|
+
[class.text-neutral-300]="activeTab !== 1"
|
|
41
|
+
class="py-2 px-1 inline-flex items-center gap-x-2 border-b-2 border-transparent text-sm whitespace-nowrap transition-colors focus:outline-none flex flex-col gap-2">
|
|
42
|
+
|
|
43
|
+
<div [ngClass]="activeTab === 1 ? 'bg-blue-100 rounded-full dark:font-semibold transition-colors' : 'bg-transparent'" class="px-4 py-2.5">
|
|
44
|
+
<svg class="shrink-0 size-5" [class.text-black]="activeTab === 1" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
|
45
|
+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
46
|
+
stroke-linejoin="round">
|
|
47
|
+
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path>
|
|
48
|
+
<polyline points="9 22 9 12 15 12 15 22"></polyline>
|
|
49
|
+
</svg>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<span class="text-lg fw-4 hover:text-blue-200">Home</span>
|
|
53
|
+
</button>
|
|
54
|
+
|
|
55
|
+
<button type="button" (click)="setActiveTab(2)" [class.font-bold]="activeTab === 2"
|
|
56
|
+
[class.text-blue-500]="activeTab === 2" [class.text-neutral-300]="activeTab !== 2"
|
|
57
|
+
class="py-2 px-1 inline-flex items-center gap-x-2 border-b-2 border-transparent text-sm whitespace-nowrap hover:text-blue-200 focus:outline-none flex flex-col gap-2">
|
|
58
|
+
<div [ngClass]="activeTab === 2 ? 'bg-blue-100 rounded-full dark:font-semibold transition-colors' : 'bg-transparent'" class="px-4 py-2.5">
|
|
59
|
+
<svg class="shrink-0 size-5" [class.text-black]="activeTab === 2" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
|
60
|
+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
61
|
+
stroke-linejoin="round">
|
|
62
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
63
|
+
<circle cx="12" cy="10" r="3"></circle>
|
|
64
|
+
<path d="M7 20.662V19a2 2 0 0 1 2-2h6a2 2 0 0 1 2 2v1.662"></path>
|
|
65
|
+
</svg>
|
|
66
|
+
</div>
|
|
67
|
+
<span class="text-lg fw-6 hover:text-blue-200">Message</span>
|
|
68
|
+
</button>
|
|
69
|
+
|
|
70
|
+
<button type="button" (click)="setActiveTab(3)" [class.font-bold]="activeTab === 3"
|
|
71
|
+
[class.text-blue-500]="activeTab === 3" [class.text-neutral-300]="activeTab !== 3"
|
|
72
|
+
class="py-2 px-1 inline-flex items-center gap-x-2 border-b-2 border-transparent text-sm whitespace-nowrap hover:text-blue-200 focus:outline-none flex flex-col gap-2">
|
|
73
|
+
<div [ngClass]="activeTab === 3 ? 'bg-blue-100 rounded-full dark:font-semibold transition-colors' : 'bg-transparent'" class="px-4 py-2.5">
|
|
74
|
+
<svg class="shrink-0 size-5" [class.text-black]="activeTab === 3" xmlns="http://www.w3.org/2000/svg" width="24" height="24"
|
|
75
|
+
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
|
76
|
+
stroke-linejoin="round">
|
|
77
|
+
<path
|
|
78
|
+
d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z">
|
|
79
|
+
</path>
|
|
80
|
+
<circle cx="12" cy="12" r="3"></circle>
|
|
81
|
+
</svg>
|
|
82
|
+
</div>
|
|
83
|
+
<span class="text-lg fw-6 hover:text-blue-200">Help</span>
|
|
84
|
+
</button>
|
|
85
|
+
</nav>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
:host {
|
|
2
|
+
|
|
3
|
+
.chat-container {
|
|
4
|
+
width: 400px !important;
|
|
5
|
+
}
|
|
6
|
+
.btn-round {
|
|
7
|
+
border-radius: 100% !important;
|
|
8
|
+
width: 40px !important;
|
|
9
|
+
height: 40px !important;
|
|
10
|
+
&.btn-fab {
|
|
11
|
+
position: fixed !important;
|
|
12
|
+
bottom: 20px !important;
|
|
13
|
+
right: 30px !important;
|
|
14
|
+
width: 60px !important;
|
|
15
|
+
height: 60px !important;
|
|
16
|
+
border-radius: 100%;
|
|
17
|
+
box-shadow: 0 6px 20px rgba(0,0,0,0.2);
|
|
18
|
+
i {
|
|
19
|
+
position: relative !important;
|
|
20
|
+
top: 0px !important;
|
|
21
|
+
right: -18px !important;
|
|
22
|
+
// margin: 10px auto !important;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
#send-btn {
|
|
27
|
+
margin-bottom: 10px !important;
|
|
28
|
+
margin-right: 0px !important;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.chatbot header {
|
|
32
|
+
padding: 10px 10px !important;
|
|
33
|
+
position: relative;
|
|
34
|
+
text-align: center;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.chatbot .chatbox {
|
|
38
|
+
overflow-y: auto;
|
|
39
|
+
height: 550px;
|
|
40
|
+
margin-bottom: 40px !important;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.chatbot :where(.chatbox, textarea)::-webkit-scrollbar {
|
|
44
|
+
width: 6px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.chatbot :where(.chatbox, textarea)::-webkit-scrollbar-track {
|
|
48
|
+
border-radius: 25px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.chatbot :where(.chatbox, textarea)::-webkit-scrollbar-thumb {
|
|
52
|
+
border-radius: 25px;
|
|
53
|
+
}
|
|
54
|
+
@media (max-width: 490px) {
|
|
55
|
+
.chatbot-toggler {
|
|
56
|
+
right: 20px;
|
|
57
|
+
bottom: 20px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.chatbot {
|
|
61
|
+
right: 0;
|
|
62
|
+
bottom: 0;
|
|
63
|
+
height: 100%;
|
|
64
|
+
border-radius: 0;
|
|
65
|
+
width: 100%;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.chatbot .chatbox {
|
|
69
|
+
height: 90%;
|
|
70
|
+
padding: 25px 15px 100px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.chatbot header span {
|
|
74
|
+
display: block;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { ChatWidgetComponent } from './chat-widget.component';
|
|
4
|
+
|
|
5
|
+
describe('ChatWidgetComponent', () => {
|
|
6
|
+
let component: ChatWidgetComponent;
|
|
7
|
+
let fixture: ComponentFixture<ChatWidgetComponent>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [ChatWidgetComponent]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(ChatWidgetComponent);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ChangeDetectionStrategy, Component, Input, ViewEncapsulation } from '@angular/core';
|
|
2
|
+
import { FormsModule } from '@angular/forms';
|
|
3
|
+
import { NgClass } from '@angular/common';
|
|
4
|
+
import { ChatHistoryComponent } from '../chat-history/chat-history.component';
|
|
5
|
+
import { HelpSupportComponent } from '../help-support/help-support.component';
|
|
6
|
+
import { HomeComponent } from '../home/home.component';
|
|
7
|
+
import { MessageComponent } from '../message/message.component';
|
|
8
|
+
import { SuccessMessageComponent } from '../success-message/success-message.component';
|
|
9
|
+
@Component({
|
|
10
|
+
selector: 'app-chat-widget',
|
|
11
|
+
standalone: true,
|
|
12
|
+
imports: [
|
|
13
|
+
FormsModule,
|
|
14
|
+
HomeComponent,
|
|
15
|
+
MessageComponent,
|
|
16
|
+
HelpSupportComponent,
|
|
17
|
+
FormsModule,
|
|
18
|
+
ChatHistoryComponent,
|
|
19
|
+
NgClass,
|
|
20
|
+
SuccessMessageComponent
|
|
21
|
+
],
|
|
22
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
23
|
+
templateUrl: './chat-widget.component.html',
|
|
24
|
+
styleUrl: './chat-widget.component.scss',
|
|
25
|
+
encapsulation: ViewEncapsulation.None
|
|
26
|
+
})
|
|
27
|
+
export class ChatWidgetComponent {
|
|
28
|
+
isTyping = false;
|
|
29
|
+
@Input() isOpen = false;
|
|
30
|
+
activeTab = 1;
|
|
31
|
+
|
|
32
|
+
setActiveTab(tab: number) {
|
|
33
|
+
this.activeTab = tab;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
navigateToTab(tab:number) {
|
|
37
|
+
this.setActiveTab(tab);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
messages = [
|
|
41
|
+
{
|
|
42
|
+
id: 1,
|
|
43
|
+
sender: 'agent',
|
|
44
|
+
text: 'Hi, I\'d like to ask some questions. Can I use Preline UI on a client project?',
|
|
45
|
+
time: '9:40 AM'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 2,
|
|
49
|
+
sender: 'agent',
|
|
50
|
+
text: 'https://preline.co/',
|
|
51
|
+
time: '9:41 AM',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 3,
|
|
55
|
+
sender: 'customer',
|
|
56
|
+
text: 'Hi, I\'d like to ask some questions. Can I use Preline UI on a client project?',
|
|
57
|
+
time: '11:27 AM'
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 4,
|
|
61
|
+
sender: 'customer',
|
|
62
|
+
text: 'Yes, you can! 😊',
|
|
63
|
+
time: '11:28 AM'
|
|
64
|
+
}
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
sendMessage(message: string) {
|
|
68
|
+
if (message.trim()) {
|
|
69
|
+
this.messages.push({
|
|
70
|
+
id: this.messages.length + 1,
|
|
71
|
+
sender: 'customer',
|
|
72
|
+
text: message,
|
|
73
|
+
time: new Date().toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })
|
|
74
|
+
});
|
|
75
|
+
console.log(message)
|
|
76
|
+
// Simulate agent typing
|
|
77
|
+
this.isTyping = true;
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
this.isTyping = false;
|
|
80
|
+
}, 2000);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<div class="rounded-lg flex flex-col overflow-hidden max-h-[600px]">
|
|
2
|
+
<div class="relative h-32 bg-gradient-to-br from-cyan-300 via-blue-600 to-orange-400 overflow-hidden">
|
|
3
|
+
<div class="absolute inset-0">
|
|
4
|
+
<div class="absolute top-0 left-0 w-24 h-24 bg-blue-700 transform rotate-45 -translate-x-8 -translate-y-8"></div>
|
|
5
|
+
<div class="absolute top-4 right-0 w-32 h-32 bg-orange-400 transform rotate-12 translate-x-12"></div>
|
|
6
|
+
<div class="absolute bottom-0 left-8 w-28 h-28 bg-cyan-400 transform -rotate-12 translate-y-12"></div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
<!-- Logo -->
|
|
11
|
+
<div class="absolute bottom-4 left-4 w-10 h-10 bg-white rounded-full flex items-center justify-center shadow-lg">
|
|
12
|
+
<svg class="w-6 h-6 text-blue-600" fill="currentColor" viewBox="0 0 24 24">
|
|
13
|
+
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z"/>
|
|
14
|
+
</svg>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="p-4 overflow-y-auto hideScroll">
|
|
19
|
+
|
|
20
|
+
<h2 class="text-lg font-semibold text-neutral-800 dark:text-neutral-200 mb-1">Send a message</h2>
|
|
21
|
+
<p class="text-sm text-neutral-500 dark:text-neutral-200 mb-6">We'll get back to you in a few hours.</p>
|
|
22
|
+
|
|
23
|
+
<form [formGroup]="contactForm" (ngSubmit)="onSubmit()" class="space-y-4">
|
|
24
|
+
|
|
25
|
+
<div>
|
|
26
|
+
<label for="name" class="block text-sm font-medium text-neutral-700 dark:text-neutral-200 mb-1">Name</label>
|
|
27
|
+
<input
|
|
28
|
+
type="text"
|
|
29
|
+
id="name"
|
|
30
|
+
formControlName="name"
|
|
31
|
+
placeholder="John Doe"
|
|
32
|
+
class="w-full px-4 py-2.5 border border-neutral-500 dark:text-neutral-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
33
|
+
[class.border-red-500]="contactForm.get('name')?.invalid && contactForm.get('name')?.touched">
|
|
34
|
+
<p *ngIf="contactForm.get('name')?.invalid && contactForm.get('name')?.touched"
|
|
35
|
+
class="text-xs text-red-500 mt-1">Name is required</p>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<!-- Email Field -->
|
|
39
|
+
<div class="relative">
|
|
40
|
+
<label for="email" class="block text-sm font-medium text-neutral-700 dark:text-neutral-200 mb-1">Email</label>
|
|
41
|
+
<input
|
|
42
|
+
type="email"
|
|
43
|
+
id="email"
|
|
44
|
+
formControlName="email"
|
|
45
|
+
placeholder="john@site.co"
|
|
46
|
+
(focus)="showTooltip = true"
|
|
47
|
+
(blur)="showTooltip = false"
|
|
48
|
+
class="w-full px-4 py-2.5 border border-neutral-500 dark:text-neutral-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
49
|
+
[class.border-red-500]="contactForm.get('email')?.invalid && contactForm.get('email')?.touched">
|
|
50
|
+
|
|
51
|
+
<!-- Tooltip -->
|
|
52
|
+
<div *ngIf="showTooltip"
|
|
53
|
+
class="absolute right-0 top-0 mt-1 mr-2 bg-neutral-900 dark:text-neutral-200 text-white text-xs px-3 py-2 rounded shadow-lg z-10 whitespace-nowrap">
|
|
54
|
+
Existing accounts should use <br>your account to access the <br>source code.
|
|
55
|
+
<div class="absolute top-1/2 -right-1 transform -translate-y-1/2 w-2 h-2 bg-neutral-900 dark:text-neutral-200 rotate-45"></div>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<p *ngIf="contactForm.get('email')?.invalid && contactForm.get('email')?.touched"
|
|
59
|
+
class="text-xs text-red-500 mt-1">
|
|
60
|
+
<span *ngIf="contactForm.get('email')?.errors?.['required']">Email is required</span>
|
|
61
|
+
<span *ngIf="contactForm.get('email')?.errors?.['email']">Invalid email format</span>
|
|
62
|
+
</p>
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<!-- Subject Field -->
|
|
66
|
+
<div>
|
|
67
|
+
<label for="subject" class="block text-sm font-medium text-neutral-700 dark:text-neutral-200 mb-1">Subject</label>
|
|
68
|
+
<input
|
|
69
|
+
type="text"
|
|
70
|
+
id="subject"
|
|
71
|
+
formControlName="subject"
|
|
72
|
+
placeholder="Preline Pro"
|
|
73
|
+
class="w-full px-4 py-2.5 border border-neutral-500 dark:text-neutral-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
74
|
+
[class.border-red-500]="contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched">
|
|
75
|
+
<p *ngIf="contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched"
|
|
76
|
+
class="text-xs text-red-500 mt-1">Subject is required</p>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Message Field -->
|
|
80
|
+
<div>
|
|
81
|
+
<label for="message" class="block text-sm font-medium text-neutral-700 dark:text-neutral-200 mb-1">How can we help?</label>
|
|
82
|
+
<textarea
|
|
83
|
+
id="message"
|
|
84
|
+
formControlName="message"
|
|
85
|
+
rows="4"
|
|
86
|
+
placeholder="Message..."
|
|
87
|
+
class="w-full px-4 py-2.5 border border-neutral-500 dark:text-neutral-200 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
|
|
88
|
+
[class.border-red-500]="contactForm.get('message')?.invalid && contactForm.get('message')?.touched"></textarea>
|
|
89
|
+
<p *ngIf="contactForm.get('message')?.invalid && contactForm.get('message')?.touched"
|
|
90
|
+
class="text-xs text-red-500 mt-1">Message is required</p>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<!-- Submit Button -->
|
|
94
|
+
<button
|
|
95
|
+
type="submit"
|
|
96
|
+
[disabled]="contactForm.invalid"
|
|
97
|
+
(click)="navigateToTab(5)"
|
|
98
|
+
class="w-full bg-blue-600 text-white py-3 rounded-lg font-medium hover:bg-blue-700 transition disabled:opacity-50 disabled:cursor-not-allowed">
|
|
99
|
+
Send message
|
|
100
|
+
</button>
|
|
101
|
+
|
|
102
|
+
</form>
|
|
103
|
+
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
|
|
3
|
+
import { HelpSupportComponent } from './help-support.component';
|
|
4
|
+
|
|
5
|
+
describe('HelpSupportComponent', () => {
|
|
6
|
+
let component: HelpSupportComponent;
|
|
7
|
+
let fixture: ComponentFixture<HelpSupportComponent>;
|
|
8
|
+
|
|
9
|
+
beforeEach(async () => {
|
|
10
|
+
await TestBed.configureTestingModule({
|
|
11
|
+
imports: [HelpSupportComponent]
|
|
12
|
+
})
|
|
13
|
+
.compileComponents();
|
|
14
|
+
|
|
15
|
+
fixture = TestBed.createComponent(HelpSupportComponent);
|
|
16
|
+
component = fixture.componentInstance;
|
|
17
|
+
fixture.detectChanges();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should create', () => {
|
|
21
|
+
expect(component).toBeTruthy();
|
|
22
|
+
});
|
|
23
|
+
});
|