feat: initial commit

This commit is contained in:
Mohamed Marrouchi
2024-09-10 10:50:11 +01:00
commit 30e5766487
879 changed files with 122820 additions and 0 deletions

View File

@@ -0,0 +1,29 @@
.sc-user-input--emoji-icon-wrapper {
background: none;
border: none;
padding: 0px;
margin: 0px;
outline: none;
}
.sc-user-input--emoji-icon-wrapper:focus {
outline: none;
}
.sc-user-input--emoji-icon {
width: 18px;
height: 18px;
cursor: pointer;
align-self: center;
vertical-align: middle;
}
.sc-user-input--emoji-icon-wrapper:focus .sc-user-input--emoji-icon path,
.sc-user-input--emoji-icon-wrapper:focus .sc-user-input--emoji-icon circle,
.sc-user-input--emoji-icon.active path,
.sc-user-input--emoji-icon.active circle,
.sc-user-input--emoji-icon:hover path,
.sc-user-input--emoji-icon:hover circle {
filter: contrast(15%);
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import React, { RefObject, useRef, useState } from 'react';
import EmojiPicker from '../EmojiPicker';
import './EmojiButton.scss';
import EmojiIcon from '../icons/EmojiIcon';
const EmojiButton: React.FC<{
inputRef: RefObject<HTMLDivElement>;
onInput: () => void;
}> = ({ inputRef, onInput }) => {
const [isActive, setIsActive] = useState(false);
const emojiButtonRef = useRef<HTMLButtonElement>(null);
const togglePicker = () => {
setIsActive(!isActive);
};
const handleBlur = (e: React.FocusEvent) => {
if (!e.relatedTarget || e.relatedTarget !== emojiButtonRef.current) {
togglePicker();
}
// if (inputRef.current) {
// inputRef.current.focus();
// }
};
const handleSelect = (
_event: React.MouseEvent<HTMLSpanElement>,
emoji: string,
) => {
insertEmoji(emoji);
};
const insertEmoji = (emoji: string) => {
if (inputRef.current) {
const textNode = document.createTextNode(emoji);
inputRef.current.appendChild(textNode);
// Place the cursor after the emoji
const range = document.createRange();
const sel = window.getSelection();
range.setStartAfter(textNode);
range.collapse(true);
sel?.removeAllRanges();
sel?.addRange(range);
onInput(); // Call to update the onChange handler
}
};
return (
<div className="sc-user-input--picker-wrapper">
{isActive && <EmojiPicker onBlur={handleBlur} onSelect={handleSelect} />}
<button
onClick={(e) => {
e.preventDefault();
togglePicker();
}}
id="sc-emoji-button"
className="sc-user-input--emoji-icon-wrapper"
ref={emojiButtonRef}
>
<EmojiIcon />
</button>
</div>
);
};
export default EmojiButton;

View File

@@ -0,0 +1,24 @@
.sc-user-input--file-wrapper {
position: relative;
cursor: pointer;
}
.sc-user-input--file-icon-wrapper {
background: none;
border: none;
padding: 0px;
margin: 0px;
outline: none;
cursor: pointer;
}
.sc-user-input--file-icon {
width: 16px;
height: 16px;
align-self: center;
outline: none;
vertical-align: middle;
}
.sc-user-input--file-icon:hover path {
filter: contrast(15%);
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import React, { ChangeEvent } from 'react';
import { useChat } from '../../providers/ChatProvider';
import FileInputIcon from '../icons/FileInputIcon';
import './FileButton.scss';
const FileButton: React.FC = () => {
const { setFile } = useChat();
const handleClick = (e: React.MouseEvent<HTMLInputElement>) => {
(e.target as HTMLInputElement).value = '';
};
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files[0]) {
setFile && setFile(e.target.files[0]);
}
};
return (
<div className="sc-user-input--file-wrapper">
<button className="sc-user-input--file-icon-wrapper" type="button">
<FileInputIcon />
<input
type="file"
id="file-input"
onChange={handleChange}
onClick={handleClick}
/>
</button>
</div>
);
};
export default FileButton;

View File

@@ -0,0 +1,30 @@
.sc-user-input--location-icon-wrapper {
background: none;
border: none;
padding: 0px;
margin: 0px;
outline: none;
}
.sc-user-input--location-icon-wrapper:focus {
outline: none;
}
.sc-user-input--location-icon {
width: 16px;
height: 16px;
cursor: pointer;
align-self: center;
vertical-align: middle;
}
.sc-user-input--location-icon-wrapper:focus .sc-user-input--location-icon path,
.sc-user-input--location-icon-wrapper:focus
.sc-user-input--location-icon
circle,
.sc-user-input--location-icon.active path,
.sc-user-input--location-icon.active circle,
.sc-user-input--location-icon:hover path,
.sc-user-input--location-icon:hover circle {
filter: contrast(15%);
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import React from 'react';
import { useChat } from '../../providers/ChatProvider';
import { useSettings } from '../../providers/SettingsProvider';
import { TOutgoingMessageType } from '../../types/message.types';
import LocationIcon from '../icons/LocationIcon';
import './LocationButton.scss';
const LocationButton: React.FC = () => {
const { setPayload, send } = useChat();
const settings = useSettings();
const locateMe = (event: React.MouseEvent<HTMLButtonElement>) => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
setPayload({
coordinates: {
lat: position.coords.latitude,
lng: position.coords.longitude,
},
});
send({
event,
source: 'geo-location',
data: {
type: TOutgoingMessageType.location,
data: {
coordinates: {
lat: position.coords.latitude,
lng: position.coords.longitude,
},
},
},
});
if (settings.autoFlush) {
setPayload(null);
}
},
(error) => {
// eslint-disable-next-line no-console
console.error('Error getting location', error);
},
);
} else {
// eslint-disable-next-line no-console
console.error('Geolocation is not supported by this browser.');
}
};
return (
<div className="sc-user-input--location-wrapper">
<button
onClick={locateMe}
type="button"
className="sc-user-input--location-icon-wrapper"
>
<LocationIcon />
</button>
</div>
);
};
export default LocationButton;

View File

@@ -0,0 +1,46 @@
.sc-user-input--menu {
align-self: center;
}
.sc-user-input--menu-button {
border: 0;
background-color: transparent;
cursor: pointer;
}
.sc-user-input--menu-img {
max-width: 24px;
}
.sc-menu-content {
display: block;
position: absolute;
box-shadow: 0px -10px 20px 5px rgba(150, 165, 190, 0.2);
width: 60%;
bottom: 55px;
z-index: 3;
&:active,
&:focus {
outline: none;
}
.sc-header-submenu-content {
position: relative;
text-align: center;
.sc-title-submenu-title {
font-weight: 600;
font-size: 1.125rem;
}
}
.sc-return-submenu-content {
display: inline-block;
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
font-size: 1.5rem;
padding: 1rem;
text-decoration: none;
cursor: pointer;
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import React, { useEffect, useRef, useState } from 'react';
import { useChat } from '../../providers/ChatProvider';
import { useColors } from '../../providers/ColorProvider';
import { useSettings } from '../../providers/SettingsProvider';
import { IMenuNode, MenuType } from '../../types/menu.type';
import { IPayload, TOutgoingMessageType } from '../../types/message.types';
import MenuIcon from '../icons/MenuIcon';
import MenuItem from '../MenuItem';
import './MenuButton.scss';
const MenuButton: React.FC = () => {
const { colors } = useColors();
const settings = useSettings();
const { send, setPayload } = useChat();
const [displayMenu, setDisplayMenu] = useState(false);
const [current, setCurrent] = useState<IMenuNode>({
title: 'Menu',
type: MenuType.nested,
call_to_actions: settings?.menu || [],
});
const menuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
setCurrent({
title: 'Menu',
type: MenuType.nested,
call_to_actions: settings?.menu || [],
});
}, [settings]);
const toggleMenu = () => {
setDisplayMenu(!displayMenu);
if (!displayMenu) {
setTimeout(() => {
menuRef.current?.focus();
}, 0);
}
};
const blur = (e: React.FocusEvent<HTMLDivElement>) => {
if (
!e.relatedTarget ||
(e.relatedTarget as HTMLElement).id !== 'sc-menu-button'
) {
setDisplayMenu(false);
}
};
const openSubItems = (item: IMenuNode) => {
setCurrent(item);
};
const handlePostback = (item: IPayload) => {
setPayload(item);
send({
// @ts-expect-error todo
event: new Event('postback'),
source: 'persistent-menu',
data: {
type: TOutgoingMessageType.postback,
data: {
payload: item.payload as string,
text: item.text as string,
},
},
});
if (settings?.autoFlush) {
setPayload(null);
}
menuRef.current?.blur();
};
const previous = (current: IMenuNode) => {
if (current._parent) {
setCurrent(current._parent);
}
};
return (
<div className="sc-user-input--menu sc-user-menu">
<button
onClick={toggleMenu}
type="button"
id="sc-menu-button"
className="sc-user-input--menu-button"
>
<MenuIcon />
</button>
{displayMenu && (
<div
tabIndex={0}
onBlur={blur}
ref={menuRef}
className="sc-menu-content"
style={{
color: colors.header.text,
backgroundColor: colors.header.bg,
}}
>
<div className="sc-header-submenu-content">
{current._parent && (
<a
style={{ color: colors.header.text }}
className="sc-return-submenu-content"
onClick={() => previous(current)}
>
&#10094;
</a>
)}
<h4 className="sc-title-submenu-title">{current.title}</h4>
</div>
{current.call_to_actions && (
<div
className="sc-menu-elements"
style={{ color: colors.header.text }}
>
{current.call_to_actions.map((subitem, index) => (
<MenuItem
key={index}
item={subitem}
parent={current}
onOpenSubItems={openSubItems}
onPostback={handlePostback}
/>
))}
</div>
)}
</div>
)}
</div>
);
};
export default MenuButton;

View File

@@ -0,0 +1,23 @@
.sc-user-input--button-icon-wrapper {
background: none;
border: none;
padding: 0px;
margin: 0px;
outline: none;
cursor: pointer;
}
.sc-user-input--button-icon-wrapper:focus {
outline: none;
}
.sc-user-input--button-icon-wrapper svg {
width: 16px;
height: 16px;
cursor: pointer;
align-self: center;
outline: none;
display: inline-block;
vertical-align: middle;
}
.sc-user-input--button-icon-wrapper svg:hover path {
filter: contrast(15%);
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright © 2024 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
*/
import React from 'react';
import SendIcon from '../icons/SendIcon';
import './SendButton.scss';
interface SendButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {}
const SendButton: React.FC<SendButtonProps> = (props) => {
const { onClick, ...rest } = props;
return (
<button
onClick={onClick}
{...rest}
className="sc-user-input--button-icon-wrapper"
>
<SendIcon />
</button>
);
};
export default SendButton;