added button to open crisp chat from the chat menu
This commit is contained in:
parent
c79c6baa1f
commit
baf59a2b78
9 changed files with 183 additions and 29 deletions
|
|
@ -14,6 +14,7 @@ import { AppThemeProvider } from 'theme/AppThemeProvider'
|
|||
import HTTPProvider from 'providers/http'
|
||||
import { Crisp } from "crisp-sdk-web";
|
||||
import { useMobile, useSnackbar } from 'hooks'
|
||||
import { initCrisp } from '../chat/CrispChat'
|
||||
// import FirmwareLoader from './FirmwareLoader'
|
||||
|
||||
const reducer = (state: GlobalState, action: GlobalStateAction): GlobalState => {
|
||||
|
|
@ -113,20 +114,14 @@ export default function UserWrapper(props: Props) {
|
|||
}, [setGlobal])
|
||||
|
||||
useEffect(() => {
|
||||
if (!crispInitialized.current && global?.user) {
|
||||
Crisp.configure(import.meta.env.VITE_CRISP_WEBSITE_ID);
|
||||
crispInitialized.current = true;
|
||||
Crisp.session.reset();
|
||||
Crisp.user.setEmail(global?.user.settings.email);
|
||||
// Optional: Set nickname or other info
|
||||
Crisp.user.setNickname(global?.user.settings.name || global?.user.settings.email);
|
||||
Crisp.user.setPhone(global?.user.settings.phoneNumber);
|
||||
|
||||
// For extra security (marks email as verified, prevents spoofing):
|
||||
// Crisp.user.setEmail(user.email, "your-hmac-signature-here"); // See Crisp docs for signing
|
||||
|
||||
// Optional: For session continuity across devices/browsers
|
||||
Crisp.setTokenId(global?.user.id()); // e.g., your DB user ID
|
||||
if (global?.user) {
|
||||
initCrisp({
|
||||
websiteId: import.meta.env.VITE_CRISP_WEBSITE_ID,
|
||||
email: global.user.settings.email,
|
||||
nickname: global.user.settings.name || global.user.settings.email,
|
||||
phone: global.user.settings.phoneNumber,
|
||||
tokenId: global.user.id(),
|
||||
});
|
||||
}
|
||||
}, [global]);
|
||||
|
||||
|
|
|
|||
BIN
src/assets/common/robotIconDark.png
Normal file
BIN
src/assets/common/robotIconDark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.5 KiB |
BIN
src/assets/common/robotIconLight.png
Normal file
BIN
src/assets/common/robotIconLight.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -1,5 +1,5 @@
|
|||
import { ChevronRight } from "@mui/icons-material";
|
||||
import { Avatar, Box, Drawer, IconButton, Theme, Typography } from "@mui/material";
|
||||
import { Avatar, Box, Divider, Drawer, IconButton, Theme, Typography } from "@mui/material";
|
||||
import { makeStyles } from "@mui/styles";
|
||||
import { useMobile } from "hooks";
|
||||
import { Team } from "models";
|
||||
|
|
@ -7,6 +7,8 @@ import Chat from "./Chat";
|
|||
import { pond } from "protobuf-ts/pond";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTeamAPI } from "providers";
|
||||
import { openCrispChat } from './CrispChat';
|
||||
import RobotIcon from "products/CommonIcons/robotIcon";
|
||||
|
||||
const useStyles = makeStyles<Theme>((theme: Theme) => ({
|
||||
chatDrawer: {
|
||||
|
|
@ -60,6 +62,11 @@ export function ChatDrawer(props: Props) {
|
|||
})
|
||||
}, [teamAPI])
|
||||
|
||||
const openCrisp = () => {
|
||||
openCrispChat()
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
open={open}
|
||||
|
|
@ -93,6 +100,12 @@ export function ChatDrawer(props: Props) {
|
|||
>
|
||||
<Avatar src={team.settings.avatar} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={openCrisp}
|
||||
>
|
||||
<RobotIcon />
|
||||
</IconButton>
|
||||
<Box width={"100%"} borderBottom={"1px solid grey"} />
|
||||
{teams.map((t, i)=> {
|
||||
if (t.settings?.key === team.key()) return null;
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -74,10 +74,23 @@ export default function ChatMessage(props: Props) {
|
|||
const mappedPerm = pond.Permission[perm as keyof typeof pond.Permission];
|
||||
return mappedPerm; // Return null for invalid keys
|
||||
}).filter(Boolean); // Optionally filter out any null values
|
||||
if (user.id() === props.chat.profile?.id) {
|
||||
perms = [
|
||||
pond.Permission.PERMISSION_READ,
|
||||
pond.Permission.PERMISSION_WRITE
|
||||
]
|
||||
}
|
||||
} else {
|
||||
// console.error("data.permissions is not an array or does not exist");
|
||||
if (user.id() === props.chat.profile?.id) {
|
||||
perms = [
|
||||
pond.Permission.PERMISSION_READ,
|
||||
pond.Permission.PERMISSION_WRITE
|
||||
]
|
||||
} else {
|
||||
perms = [];
|
||||
}
|
||||
}
|
||||
return perms
|
||||
}
|
||||
const permissions = generatePermissions()
|
||||
|
|
|
|||
111
src/chat/CrispChat.ts
Normal file
111
src/chat/CrispChat.ts
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import { Crisp } from "crisp-sdk-web";
|
||||
|
||||
const CRISP_HIDDEN_STYLE_ID = "crisp-hide-override";
|
||||
const ANIMATION_DURATION_MS = 300;
|
||||
|
||||
let initialized = false;
|
||||
|
||||
/**
|
||||
* Initialize Crisp and immediately hide the default chat button.
|
||||
* Call this once on app load (e.g., in UserWrapper after user data is ready).
|
||||
*/
|
||||
export function initCrisp(opts: {
|
||||
websiteId: string;
|
||||
email?: string;
|
||||
nickname?: string;
|
||||
phone?: string;
|
||||
tokenId?: string;
|
||||
}) {
|
||||
if (initialized) return;
|
||||
|
||||
Crisp.configure(opts.websiteId);
|
||||
Crisp.session.reset();
|
||||
|
||||
if (opts.email) Crisp.user.setEmail(opts.email);
|
||||
if (opts.nickname) Crisp.user.setNickname(opts.nickname);
|
||||
if (opts.phone) Crisp.user.setPhone(opts.phone);
|
||||
if (opts.tokenId) Crisp.setTokenId(opts.tokenId);
|
||||
|
||||
initialized = true;
|
||||
|
||||
// Hide the chat button off-screen
|
||||
injectCrispStyles();
|
||||
|
||||
// Auto-hide when the user closes the chat
|
||||
Crisp.chat.onChatClosed(() => {
|
||||
hideCrispChatButton();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject base styles that keep Crisp hidden until we want it.
|
||||
* Uses opacity + visibility so Crisp keeps its default bottom-right positioning.
|
||||
*/
|
||||
function injectCrispStyles() {
|
||||
let style = document.getElementById(CRISP_HIDDEN_STYLE_ID);
|
||||
if (!style) {
|
||||
style = document.createElement("style");
|
||||
style.id = CRISP_HIDDEN_STYLE_ID;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
|
||||
style.textContent = `
|
||||
#crisp-chatbox {
|
||||
z-index: 99999 !important;
|
||||
transition: opacity ${ANIMATION_DURATION_MS}ms ease-in-out,
|
||||
visibility ${ANIMATION_DURATION_MS}ms ease-in-out !important;
|
||||
opacity: 0 !important;
|
||||
visibility: hidden !important;
|
||||
pointer-events: none !important;
|
||||
}
|
||||
#crisp-chatbox.crisp-visible {
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the Crisp widget and open the chat window.
|
||||
* Safe to call from any onClick handler.
|
||||
*/
|
||||
export function openCrispChat() {
|
||||
const chatbox = document.getElementById("crisp-chatbox");
|
||||
if (chatbox) {
|
||||
chatbox.classList.add("crisp-visible");
|
||||
setTimeout(() => {
|
||||
Crisp.chat.open();
|
||||
}, ANIMATION_DURATION_MS);
|
||||
} else {
|
||||
Crisp.chat.open();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the chat window and hide the widget.
|
||||
*/
|
||||
export function closeCrispChat() {
|
||||
Crisp.chat.close();
|
||||
hideCrispChatButton();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the default Crisp button without opening the chat.
|
||||
*/
|
||||
export function showCrispButton() {
|
||||
const chatbox = document.getElementById("crisp-chatbox");
|
||||
if (chatbox) {
|
||||
chatbox.classList.add("crisp-visible");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the default Crisp button.
|
||||
*/
|
||||
export function hideCrispChatButton() {
|
||||
const chatbox = document.getElementById("crisp-chatbox");
|
||||
if (chatbox) {
|
||||
chatbox.classList.remove("crisp-visible");
|
||||
}
|
||||
}
|
||||
|
|
@ -323,21 +323,21 @@ export default function Devices() {
|
|||
return false
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log("has no2: "+hasNo2)
|
||||
}, [hasNo2])
|
||||
// useEffect(() => {
|
||||
// console.log("has no2: "+hasNo2)
|
||||
// }, [hasNo2])
|
||||
|
||||
useEffect(() => {
|
||||
console.log("has co2: "+hasCo2)
|
||||
}, [hasCo2])
|
||||
// useEffect(() => {
|
||||
// console.log("has co2: "+hasCo2)
|
||||
// }, [hasCo2])
|
||||
|
||||
useEffect(() => {
|
||||
console.log("has co: "+hasNo2)
|
||||
}, [hasNo2])
|
||||
// useEffect(() => {
|
||||
// console.log("has co: "+hasNo2)
|
||||
// }, [hasNo2])
|
||||
|
||||
useEffect(() => {
|
||||
console.log("has o2: "+hasO2)
|
||||
}, [hasO2])
|
||||
// useEffect(() => {
|
||||
// console.log("has o2: "+hasO2)
|
||||
// }, [hasO2])
|
||||
|
||||
const openProvisionDialog = () => {
|
||||
setIsProvisionDialogOpen(true);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import MarketWhite from "assets/marketplaceImages/marketplaceIconLight.png";
|
|||
import MarketBlack from "assets/marketplaceImages/marketplaceIconDark.png";
|
||||
import { ImgIcon } from "common/ImgIcon";
|
||||
import { useThemeType } from "hooks";
|
||||
import React from "react";
|
||||
|
||||
interface Props {
|
||||
type?: "light" | "dark";
|
||||
|
|
|
|||
23
src/products/CommonIcons/robotIcon.tsx
Normal file
23
src/products/CommonIcons/robotIcon.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import RobotWhite from "assets/common/robotIconLight.png";
|
||||
import RobotBlack from "assets/common/robotIconDark.png";
|
||||
import { ImgIcon } from "common/ImgIcon";
|
||||
import { useThemeType } from "hooks";
|
||||
|
||||
interface Props {
|
||||
type?: "light" | "dark";
|
||||
}
|
||||
|
||||
export default function RobotIcon(props: Props) {
|
||||
const themeType = useThemeType();
|
||||
const { type } = props;
|
||||
|
||||
const src = () => {
|
||||
if (type) {
|
||||
return type === "light" ? RobotWhite : RobotBlack;
|
||||
}
|
||||
|
||||
return themeType === "light" ? RobotBlack : RobotWhite;
|
||||
};
|
||||
|
||||
return <ImgIcon alt="marketplace" src={src()} />;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue