lulichat 1.0.2 → 1.0.3

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 (42) hide show
  1. package/README.md +1 -0
  2. package/dist/components/ChatIcon.d.ts +5 -0
  3. package/dist/components/ChatInterface.d.ts +9 -0
  4. package/dist/components/ChatWidget.d.ts +7 -0
  5. package/dist/components/ChatWidget.test.d.ts +1 -0
  6. package/dist/components/ContactForm.d.ts +11 -0
  7. package/dist/components/LuliChat.d.ts +3 -0
  8. package/dist/components/LuliChat.test.d.ts +1 -0
  9. package/dist/components/ui/Button.d.ts +8 -0
  10. package/dist/components/ui/Form.d.ts +33 -0
  11. package/dist/components/ui/Icons.d.ts +10 -0
  12. package/dist/components/ui/Input.d.ts +9 -0
  13. package/dist/constants.d.ts +32 -0
  14. package/dist/hooks/useSocket.d.ts +4 -0
  15. package/dist/index.d.ts +4 -0
  16. package/dist/lib/socket.d.ts +17 -0
  17. package/dist/lulichat-support.es.js +200 -202
  18. package/dist/lulichat-support.umd.js +10 -10
  19. package/dist/main.d.ts +1 -0
  20. package/dist/types/index.d.ts +85 -0
  21. package/dist/utils/api.d.ts +15 -0
  22. package/dist/utils/index.d.ts +2 -0
  23. package/package.json +12 -5
  24. package/src/components/ChatIcon.tsx +0 -27
  25. package/src/components/ChatInterface.tsx +0 -211
  26. package/src/components/ChatWidget.tsx +0 -198
  27. package/src/components/ContactForm.tsx +0 -155
  28. package/src/components/LuliChat.tsx +0 -48
  29. package/src/components/ui/Button.tsx +0 -44
  30. package/src/components/ui/Form.tsx +0 -174
  31. package/src/components/ui/Icons.tsx +0 -443
  32. package/src/components/ui/Input.tsx +0 -31
  33. package/src/constants.ts +0 -32
  34. package/src/hooks/useSocket.ts +0 -45
  35. package/src/index.css +0 -383
  36. package/src/index.ts +0 -5
  37. package/src/lib/socket.ts +0 -96
  38. package/src/main.tsx +0 -15
  39. package/src/types/index.ts +0 -93
  40. package/src/utils/api.ts +0 -59
  41. package/src/utils/index.ts +0 -5
  42. package/src/vite-env.d.ts +0 -1
@@ -1,198 +0,0 @@
1
- import React, { useState, useEffect } from "react";
2
- import {
3
- LuliChatConfig,
4
- ChatState,
5
- ContactInfo,
6
- CompanyInfo,
7
- ChatSession,
8
- } from "../types";
9
- import { LuliChatAPI } from "@/utils/api";
10
- import { ContactForm } from "./ContactForm";
11
- import { ChatInterface } from "./ChatInterface";
12
- import { Loader, MessageCircle } from "./ui/Icons";
13
- import Button from "./ui/Button";
14
- import Input from "./ui/Input";
15
- import { Attachment, Send } from "./ui/Icons";
16
-
17
- interface ChatWidgetProps {
18
- config: LuliChatConfig;
19
- }
20
-
21
- export const ChatWidget: React.FC<ChatWidgetProps> = ({ config }) => {
22
- const [chatState, setChatState] = useState<ChatState>("contact-form");
23
- const [companyInfo, setCompanyInfo] = useState<CompanyInfo["data"] | null>(
24
- null
25
- );
26
- const [chatSession, setChatSession] = useState<ChatSession | null>(null);
27
- const [isLoading, setIsLoading] = useState(false);
28
- const [error, setError] = useState<string | null>(null);
29
- const [open, setOpen] = React.useState(false);
30
-
31
- const api = new LuliChatAPI(config);
32
-
33
- useEffect(() => {
34
- loadCompanyInfo();
35
- // eslint-disable-next-line react-hooks/exhaustive-deps
36
- }, [config]);
37
-
38
- const loadCompanyInfo = async () => {
39
- try {
40
- const info = await api.getCompanyInfo();
41
- setCompanyInfo(info.data);
42
- } catch (err) {
43
- setError("Failed to load chat configuration");
44
- console.error("Failed to load company info:", err);
45
- }
46
- };
47
-
48
- const handleContactSubmit = async (contactInfo: ContactInfo) => {
49
- setIsLoading(true);
50
- try {
51
- const response = await api.submitContactInfo(contactInfo);
52
- const session: ChatSession = {
53
- id: response.sessionId,
54
- token: response.token,
55
- isActive: true,
56
- };
57
- setChatSession(session);
58
- setChatState("chat");
59
- } catch (err) {
60
- setError("Failed to start chat. Please try again.");
61
- console.error("Failed to submit contact info:", err);
62
- } finally {
63
- setIsLoading(false);
64
- }
65
- };
66
-
67
- const startAnonymousChat = async () => {
68
- setIsLoading(true);
69
- try {
70
- const response = await api.startAnonymousChat();
71
- const session: ChatSession = {
72
- id: response.sessionId,
73
- token: response.token,
74
- isActive: true,
75
- };
76
- setChatSession(session);
77
- setChatState("chat");
78
- } catch (err) {
79
- setError("Failed to start chat. Please try again.");
80
- console.error("Failed to start anonymous chat:", err);
81
- } finally {
82
- setIsLoading(false);
83
- }
84
- };
85
-
86
- const handleCloseChat = () => {
87
- setChatState("closed");
88
- setChatSession(null);
89
- setError(null);
90
- };
91
-
92
- console.log({ companyInfo });
93
-
94
- return (
95
- <div className={`lulichat lulichat-${config.position}`}>
96
- {open && (
97
- <div className="lulichat-main">
98
- {error && (
99
- <div className="p-4 bg-destructive/10 border-b">
100
- <p className="text-sm text-destructive">{error}</p>
101
- <button onClick={() => setError(null)} className="mt-2">
102
- Retry
103
- </button>
104
- </div>
105
- )}
106
-
107
- <ContactForm
108
- companyName={companyInfo?.name!}
109
- onSubmit={handleContactSubmit}
110
- onSkip={config.allowAnonymous ? startAnonymousChat : undefined}
111
- allowAnonymous={config.allowAnonymous || false}
112
- isLoading={isLoading}
113
- />
114
-
115
- <div style={{ padding: 16 }}>
116
- <div
117
- style={{
118
- display: "flex",
119
- gap: 10,
120
- flexWrap: "wrap",
121
- }}
122
- >
123
- {companyInfo?.queues.map((queue) => {
124
- return (
125
- <div className="lulichat-tag" key={queue.id}>
126
- {queue.name?.toLocaleLowerCase()}
127
- </div>
128
- );
129
- })}
130
- </div>
131
-
132
- <div
133
- style={{
134
- height: 2,
135
- backgroundColor: "hsl(var(--border))",
136
- marginBlock: 16,
137
- }}
138
- />
139
-
140
- <div
141
- style={{
142
- display: "flex",
143
- alignItems: "center",
144
- columnGap: 10,
145
- }}
146
- className="form-control"
147
- >
148
- <Input
149
- name="message"
150
- autoComplete="off"
151
- style={{ border: "none", paddingInline: 0 }}
152
- placeholder="What can we help you with ?."
153
- />
154
- <div className="lulichat-btn-group">
155
- <Button style={{ color: "#00000080" }} size="icon">
156
- <Attachment height={24} />
157
- </Button>
158
- <Button style={{ color: "#00000080" }} size="icon">
159
- <Send />
160
- </Button>
161
- </div>
162
- </div>
163
- </div>
164
-
165
- {chatState === "chat" && chatSession && companyInfo && (
166
- <ChatInterface
167
- session={chatSession}
168
- companyInfo={companyInfo}
169
- onClose={handleCloseChat}
170
- />
171
- )}
172
- </div>
173
- )}
174
-
175
- <Button
176
- onClick={() => setOpen(!open)}
177
- className=""
178
- shape="circle"
179
- size="icon"
180
- style={{
181
- backgroundColor: config.primaryColor,
182
- height: "4rem",
183
- width: "4rem",
184
- padding: 0,
185
- color: "#fff",
186
- alignSelf: "end",
187
- }}
188
- disabled={!companyInfo && !error}
189
- >
190
- {!companyInfo && !error ? (
191
- <Loader />
192
- ) : (
193
- <MessageCircle height={40} width={40} />
194
- )}
195
- </Button>
196
- </div>
197
- );
198
- };
@@ -1,155 +0,0 @@
1
- import { ContactInfo } from "@/types";
2
- import React, { useState } from "react";
3
- import Input from "./ui/Input";
4
- import Form from "./ui/Form";
5
- import { DropdownArrow, Hello, Loader } from "./ui/Icons";
6
- import Button from "./ui/Button";
7
-
8
- interface ContactFormProps {
9
- companyName: string;
10
- onSubmit: (contactInfo: ContactInfo) => void;
11
- onSkip?: () => void;
12
- allowAnonymous: boolean;
13
- isLoading: boolean;
14
- }
15
-
16
- export const ContactForm: React.FC<ContactFormProps> = ({
17
- companyName,
18
- onSubmit,
19
- onSkip,
20
- allowAnonymous,
21
- isLoading,
22
- }) => {
23
- const [open, setOpen] = React.useState(false);
24
- const [formData, setFormData] = useState<ContactInfo>({
25
- name: "",
26
- email: "",
27
- phone: "",
28
- company: "",
29
- });
30
-
31
- const [errors, setErrors] = useState<Partial<ContactInfo>>({});
32
- const [values, setValues] = React.useState<Record<string, string>>({
33
- email: "",
34
- name: "",
35
- });
36
-
37
- const validateForm = () => {
38
- const newErrors: Partial<ContactInfo> = {};
39
-
40
- if (!formData.name.trim()) {
41
- newErrors.name = "Name is required";
42
- }
43
-
44
- if (!formData.email.trim()) {
45
- newErrors.email = "Email is required";
46
- } else if (!/\S+@\S+\.\S+/.test(formData.email)) {
47
- newErrors.email = "Email is invalid";
48
- }
49
-
50
- setErrors(newErrors);
51
- return Object.keys(newErrors).length === 0;
52
- };
53
-
54
- const handleSubmit = (e: React.FormEvent) => {
55
- e.preventDefault();
56
- if (validateForm()) {
57
- onSubmit(formData);
58
- }
59
- };
60
-
61
- const handleInputChange = (field: keyof ContactInfo, value: string) => {
62
- setFormData((prev) => ({ ...prev, [field]: value }));
63
- if (errors[field]) {
64
- setErrors((prev) => ({ ...prev, [field]: undefined }));
65
- }
66
- };
67
-
68
- return (
69
- <Form
70
- autoComplete="off"
71
- onSubmit={handleSubmit}
72
- onValuesChange={setValues}
73
- className="lulichat-contact-form"
74
- >
75
- <div className="lulichat-form-header">
76
- <div
77
- style={{
78
- marginBottom: "20px",
79
- display: "flex",
80
- alignItems: "end",
81
- columnGap: 8,
82
- }}
83
- >
84
- <Hello
85
- style={{ display: "block" }}
86
- color="#DEDEDE6A"
87
- height={40}
88
- width={50}
89
- />
90
- <h3 style={{ fontWeight: 500 }}>Hello,</h3>
91
- </div>
92
- <h3 style={{ marginBottom: 8 }} className="lulichat-title">
93
- Welcome to {companyName} Live Chat
94
- </h3>
95
- <p
96
- role="button"
97
- onClickCapture={() => setOpen(!open)}
98
- style={{
99
- textDecoration: "underline",
100
- lineHeight: "1rem",
101
- cursor: "pointer",
102
- }}
103
- >
104
- Please provide your details for a better support experience{" "}
105
- <DropdownArrow
106
- style={{
107
- display: "inline-block",
108
- marginBottom: -2,
109
- }}
110
- height={20}
111
- width={20}
112
- />
113
- </p>
114
- </div>
115
- <div className={`lulichat-form-group${open ? " open" : ""}`}>
116
- <Form.Item error={errors.name} label="Name (Optional)" name="name">
117
- <Input
118
- id="name"
119
- placeholder="Enter your name"
120
- type="text"
121
- value={formData.name}
122
- onChange={(e) => handleInputChange("name", e.target.value)}
123
- className={"transparent" + (errors.name ? " error" : "")}
124
- />
125
- </Form.Item>
126
-
127
- <Form.Item name="email" error={errors.email} label="Email">
128
- <Input
129
- id="email"
130
- type="email"
131
- placeholder="Enter your valid email"
132
- value={formData.email}
133
- onChange={(e) => handleInputChange("email", e.target.value)}
134
- className={"transparent" + (errors.name ? " error" : "")}
135
- />
136
- </Form.Item>
137
- <Button
138
- type="submit"
139
- disabled={isLoading || !values.email}
140
- style={{ width: "100%", marginTop: 10 }}
141
- className="lulichat-contact-form-btn"
142
- >
143
- {isLoading ? (
144
- <>
145
- <Loader />
146
- Submitting...
147
- </>
148
- ) : (
149
- "Submit"
150
- )}
151
- </Button>
152
- </div>
153
- </Form>
154
- );
155
- };
@@ -1,48 +0,0 @@
1
- import React from "react";
2
- import { ChatWidget } from "./ChatWidget";
3
- import { LuliChatConfig } from "../types";
4
-
5
- interface LuliChatProps {
6
- apiKey: string;
7
- baseUrl?: string;
8
- position?: "bottom-right" | "bottom-left" | "top-right" | "top-left";
9
- primaryColor?: string;
10
- companyName?: string;
11
- welcomeMessage?: string;
12
- requireContactInfo?: boolean;
13
- allowAnonymous?: boolean;
14
- }
15
-
16
- export const LuliChat: React.FC<LuliChatProps> = ({
17
- apiKey,
18
- baseUrl,
19
- position = "bottom-right",
20
- primaryColor = "#007bff",
21
- companyName = "Support",
22
- welcomeMessage = "Hello! How can we help you today?",
23
- requireContactInfo = true,
24
- allowAnonymous = true,
25
- }) => {
26
- const config: LuliChatConfig = {
27
- apiKey,
28
- baseUrl,
29
- position,
30
- primaryColor,
31
- companyName,
32
- welcomeMessage,
33
- requireContactInfo,
34
- allowAnonymous,
35
- };
36
-
37
- if (!apiKey) {
38
- console.error("LuliChat: API key is required");
39
- return null;
40
- }
41
-
42
- return <ChatWidget config={config} />;
43
- };
44
-
45
- // Export all types and components for external use
46
- export * from "../types";
47
- export { LuliChatAPI } from "../utils/api";
48
- export { ChatSocket } from "../lib/socket";
@@ -1,44 +0,0 @@
1
- import React from "react";
2
- import clsx from "clsx";
3
-
4
- interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
5
- variant?:
6
- | "primary"
7
- | "destructive"
8
- | "outline"
9
- | "secondary"
10
- | "ghost"
11
- | "link";
12
- size?: "md" | "sm" | "lg" | "icon";
13
- shape?: "circle" | "rounded" | "none";
14
- }
15
-
16
- const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
17
- (
18
- {
19
- className,
20
- variant = "outline",
21
- size = "md",
22
- shape = "rounded",
23
- ...props
24
- },
25
- ref
26
- ) => {
27
- return (
28
- <button
29
- className={clsx(
30
- "lulichat-btn",
31
- `lulichat-btn-${variant}`,
32
- `lulichat-btn-${size}`,
33
- `lulichat-btn-${shape}`,
34
- className
35
- )}
36
- ref={ref}
37
- {...props}
38
- />
39
- );
40
- }
41
- );
42
- Button.displayName = "Button";
43
-
44
- export default Button;
@@ -1,174 +0,0 @@
1
- import { cn } from "@/utils";
2
- import React, { useRef, useImperativeHandle, useCallback } from "react";
3
- import Input from "./Input";
4
-
5
- interface FormProps<T> extends React.FormHTMLAttributes<HTMLFormElement> {
6
- onValuesChange?: (values: T) => void;
7
- initialValues?: T;
8
- onFinish?: (values: T) => void;
9
- }
10
-
11
- export interface FormRefObject<T> {
12
- getValues(): T;
13
- getFieldValue: <K extends keyof T>(fieldName: K) => T[K];
14
- elementRef: () => HTMLFormElement | null;
15
- setFieldValue: <K extends keyof T>(fieldName: K, value: T[K]) => void;
16
- }
17
-
18
- const FormContext = React.createContext<FormRefObject<any> | undefined>(
19
- undefined
20
- );
21
-
22
- type FormComponent = React.ForwardRefExoticComponent<
23
- FormProps<Record<string, any>> &
24
- React.RefAttributes<FormRefObject<Record<string, any>>>
25
- > & {
26
- Item: typeof FormItem;
27
- Label: typeof FormLabel;
28
- useForm: typeof useForm;
29
- };
30
-
31
- const Form: FormComponent = React.forwardRef(
32
- <T extends Record<string, any>>(
33
- { children, onValuesChange, initialValues, ...props }: FormProps<T>,
34
- ref: React.Ref<FormRefObject<T>>
35
- ) => {
36
- const values = useRef<T>(initialValues || ({} as T));
37
- const self = useRef<HTMLFormElement>(null);
38
-
39
- const setFieldValue = useCallback(
40
- <K extends keyof T>(fieldName: K, value: T[K]) => {
41
- values.current[fieldName] = value;
42
- if (onValuesChange) onValuesChange({ ...values.current });
43
- },
44
- [onValuesChange]
45
- );
46
-
47
- const methods: FormRefObject<T> = {
48
- getValues: () => values.current!,
49
- getFieldValue: (fieldName) => values.current?.[fieldName]!,
50
- elementRef: () => self.current,
51
- setFieldValue,
52
- };
53
-
54
- useImperativeHandle(ref, () => methods);
55
-
56
- return (
57
- <FormContext.Provider value={methods}>
58
- <form {...props} ref={self} onSubmit={props.onSubmit}>
59
- {children}
60
- </form>
61
- </FormContext.Provider>
62
- );
63
- }
64
- ) as FormComponent;
65
-
66
- interface FormItemProps extends React.HTMLAttributes<HTMLDivElement> {
67
- name?: string;
68
- children?: React.ReactNode;
69
- label?: string;
70
- required?: boolean;
71
- error?: string;
72
- }
73
-
74
- const FormItem = React.forwardRef<HTMLDivElement, FormItemProps>(
75
- ({ name, children, ...props }, ref) => {
76
- const context = React.useContext(FormContext)!;
77
-
78
- const handleChange = function (
79
- this: React.ReactElement<
80
- React.InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement>
81
- >,
82
- e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
83
- ) {
84
- if (context && name) {
85
- context.setFieldValue(name, e.target.value);
86
- }
87
-
88
- this.props.onChange?.(e);
89
- };
90
-
91
- // Clone children and inject value and onChange if name is provided
92
- const enhancedChildren = React.Children.map(children, (child) => {
93
- if (React.isValidElement(child) && name && child.type === Input) {
94
- let _child = child as React.ReactElement<
95
- React.InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement>
96
- >;
97
- return React.cloneElement(_child, {
98
- ..._child.props,
99
- name,
100
- value: context?.getFieldValue(name) ?? "",
101
- onChange: handleChange.bind(_child),
102
- required: props.required,
103
- });
104
- }
105
- return child;
106
- });
107
-
108
- const _props = Object.create(props);
109
- delete _props.required;
110
- delete _props.label;
111
-
112
- return (
113
- <div className="lulichat-form-item" ref={ref} {..._props}>
114
- {props.label && (
115
- <FormLabel htmlFor={name} required={props.required}>
116
- {props.label}
117
- </FormLabel>
118
- )}
119
- {enhancedChildren}
120
- {props.error && <p className="error">{props.error}</p>}
121
- </div>
122
- );
123
- }
124
- );
125
-
126
- interface FormLabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
127
- required?: boolean;
128
- htmlFor?: string;
129
- }
130
-
131
- const FormLabel = React.forwardRef<HTMLLabelElement, FormLabelProps>(
132
- (props, ref) => {
133
- const required = props.required;
134
- const children = props.children;
135
-
136
- return (
137
- <label
138
- {...props}
139
- className={cn("lulichat-form-label", props.className)}
140
- ref={ref}
141
- >
142
- {children}
143
- {required && <b style={{ color: "red" }}>*</b>}
144
- </label>
145
- );
146
- }
147
- );
148
-
149
- const useForm = <T,>(initialValues: T) => {
150
- const values = useRef<T>(initialValues || ({} as T));
151
- const self = useRef<HTMLFormElement>(null);
152
-
153
- const setFieldValue = useCallback(
154
- <K extends keyof T>(fieldName: K, value: T[K]) => {
155
- values.current[fieldName] = value;
156
- },
157
- []
158
- );
159
-
160
- const methods: FormRefObject<T> = {
161
- getValues: () => values.current!,
162
- getFieldValue: (fieldName) => values.current?.[fieldName]!,
163
- elementRef: () => self.current,
164
- setFieldValue,
165
- };
166
-
167
- return [methods];
168
- };
169
-
170
- Form.Item = FormItem;
171
- Form.Label = FormLabel;
172
- Form.useForm = useForm;
173
-
174
- export default Form;