import { PaperAirplaneIcon, StarIcon } from "@heroicons/react/24/solid";
import React, { useEffect, useState, useRef } from "react";
import LoadingBar from "react-top-loading-bar";
import useFade from "/components/useFade";
import Welcome from "/components/Welcome";
import { useRouter } from "next/router";
import moment from "moment-timezone";
import CGU from "/components/CGU";
import cookie from "js-cookie";
import _ from "lodash";
type Choice = {
title: string;
// Include any other properties you expect in a choice object
};
type Response = {
id: string;
type: string;
text: string;
user: boolean;
date: string;
choices?: Choice[];
};
interface Props {
err?: string;
cgu: string;
}
interface Payload {
message: string;
keypwd?: string | string[];
fiche?: boolean;
[key: string]: string | string[] | boolean | undefined;
}
const Chat = (props: Props) => {
const router = useRouter();
const msgCGU = `En conversant avec moi, vous acceptez les Conditions Générales d'Utilisation, toutes vos informations sont confidentielles et uniquement à destination de votre praticien et de son équipe. Cliquez sur ce message pour les consulter.`;
const [activeConversation, setActiveConversation] = useState(false);
const [alrt, setAlrt] = useState<string>(props.err ? props.err : "none");
const [messagesg, setMessagesg] = useState<Response[]>([]);
const [isVisible, setVisible, fadeProps] = useFade(false);
const [messages, setMessages] = useState<Response[]>([]);
const [transition, setTransition] = useState(true);
const [closing, setClosing] = useState(false);
const [selected, setSelected] = useState([]);
const [authed, setAuthed] = useState(false);
const [typing, setTyping] = useState(false);
const [progress, setProgress] = useState(0);
const [clicked, setClicked] = useState([]);
const [input, setInput] = useState("");
const [con, setCon] = useState(true);
const lastMsg = useRef<HTMLLIElement>(null);
const messagesEndRef = useRef<HTMLDivElement>(null);
const responsivness = 0.5;
const sessionTime = 5;
const Timeout = (time: number): AbortController => {
const controller = new AbortController();
setTimeout(() => controller.abort(), time * 1000);
return controller;
};
useEffect(() => {
const interval = setInterval(async () => {
try {
const res = await fetch("https://api.github.com/users/microsoft", {
signal: Timeout(5).signal,
});
if (!res.ok) throw new Error("Fetch failed");
const ping = await res.json();
if (!ping) setCon(false);
} catch (error) {
console.error(error);
}
}, 10000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
const getThatDamnData = async () => {
await fetch(`/api/messages?keypwd=${router.query.pid}`)
.then(async (res) => {
if (res.ok) {
return res.json();
} else {
throw await res.json();
}
})
.then(async (data) => {
if (data && authed) {
if (
data.length < 1 ||
(messages[0] && messages[0].text === "bonjour")
) {
const pl1: Payload = {
message: "Bonjour",
keypwd: router.query.pid,
fiche: router.query.f ? true : false,
};
Object.entries(router.query).map((e) => {
if (e[0] !== "pid" && e[0] !== "f") {
pl1[e[0]] = e[1];
}
});
await fetch("/api/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(pl1),
})
.then(async (res) => {
if (res.ok) {
setAlrt("none");
setActiveConversation(true);
return await res.json();
} else {
throw await res.json();
}
})
.then((str) => {
if (str) {
setMessagesg(str);
setMessages(str);
setProgress(100);
}
})
.catch((err) => {
setAlrt(err.alrt);
});
}
const temp = [...data]
.map((m) => {
if (m.includes("triggerbp-")) {
window.location.href =
"/fin?keypwd=" +
router.query.pid +
"&mesg=" +
JSON.parse(m).text;
} else return m[0] === "{" && JSON.parse(m);
})
.filter((m) => m)
.map((m) => {
if (m.text.includes("_opencgu")) {
return {
...m,
text: msgCGU,
};
}
return m;
});
if (activeConversation) {
if (temp.length > messagesg.length) {
setActiveConversation(true);
setMessagesg(temp);
} else if (messagesg.length > messages.length) {
setTimeout(async () => {
setMessages([...messages, messagesg[messages.length]]);
}, responsivness * 1000);
} else {
messagesg.length === messages.length && setTyping(false);
}
} else {
setMessages(temp);
setMessagesg(temp);
setActiveConversation(true);
}
cookie.set("auth", moment().format("YYYY-MM-DD HH:mm:ss"), {
expires: new Date(new Date().getTime() + sessionTime * 60 * 1000),
});
}
})
.catch(async (err) => {
setAlrt(await err.alrt);
});
};
const timer = setInterval(() => {
getThatDamnData();
}, 2000);
getThatDamnData();
scrollDown();
return () => clearTimeout(timer);
}, []);
useEffect(() => {
if (!cookie.get("auth")) {
fetch("/api/messages", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
del: true,
keypwd: router.query.pid,
j: router.query.j,
}),
})
.then(async (res) => {
if (!res.ok) {
throw await res.json();
}
})
.catch((err) => {
console.log(err);
});
}
setAuthed(cookie.get("auth") || router.query.f ? true : false);
}, [router.query.f, router.query.j, router.query.pid]);
const scrollDown = () => {
setTimeout(() => {
lastMsg.current?.scrollIntoView({ behavior: "smooth" } );
}, 350);
};
useEffect(() => {
if (authed || router.query.f) {
cookie.set("auth", moment().format("YYYY-MM-DD HH:mm:ss"), {
expires: new Date(new Date().getTime() + sessionTime * 60 * 1000),
});
}
}, [authed, messages, router.query.f]);
const handleSubmit = async (val: string) => {
if (val !== "") {
setAlrt("none");
setTyping(true);
scrollDown();
setProgress(70);
setMessages([
...messages,
{
id: new Date().valueOf() + Math.random().toString(),
type: "text",
text: val,
user: true,
date: moment().tz("Europe/Paris").format("HH:mm"),
},
]);
const pl3: Payload = {
message: val,
keypwd: router.query.pid,
fiche: router.query.f ? true : false,
};
Object.entries(router.query).map((e) => {
if (e[0] !== "pid" && e[0] !== "f") {
pl3[e[0]] = e[1];
}
});
await fetch("/api/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(pl3),
})
.then(async (res) => {
if (res.ok) {
return res.json();
} else {
throw await res.json();
}
})
.then((str) => {
if (str) {
scrollDown();
}
})
.catch((err) => {
setAlrt(err.alrt);
})
.finally(() => {
setInput("");
setProgress(100);
});
}
};
const handleState = () => {
isVisible ? setClosing(true) : setClosing(false);
setVisible(!isVisible);
};
const scrollToBottom = () => {
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
};
useEffect(() => {
setTimeout(() => {
scrollToBottom();
}, 300);
}, [messages, authed]);
return typeof router.query.f !== "undefined" && !authed ? (
<Welcome setAuthed={setAuthed} />
) : (
authed && (
<div className="Chat">
<div className={`alrt ${alrt}`}></div>
<header>
<div className="img">
<img
src="/images/chatbot2.png"
alt="logo bot"
width={42}
height={46}
/>
</div>
<span>En ligne</span>
<h1>ASISPO</h1>
{typing && (
<div className="typing">
<div className="inner">
<span></span>
<span></span>
<span></span>
</div>
</div>
)}
</header>
{!con && <div className="alrtMsg">Vérifiez votre connexion...</div>}
<div className="chat">
{transition && (
<ul>
<>
{authed &&
(messages.length > 0 ? (
messages.map((msg, mi: number) => {
if (
typeof msg.text !== "undefined" &&
msg.text !== "Bonjour" &&
msg.text[0] !== "_"
) {
return (
<li
className={msg.user ? "user-msg" : ""}
key={mi}
ref={messages.length === mi + 1 ? lastMsg : null}
onClick={(e) => {
if (msg.text === msgCGU) {
handleState();
} else {
e.preventDefault();
}
}}
>
<>
<span className="date">{msg.date}</span>
<div className="botImg">
<img
src="/images/bot-mini.png"
alt="bot image"
className="img"
/>
</div>
<p>{msg.text}</p>
{msg.choices && (
<div className="pbtn">
<div
className={
msg.choices.length > 4 &&
msg.choices[0]?.title === "5" &&
msg.choices[4]?.title === "1" &&
!msg.choices[5]
? "choices stars"
: "choices"
}
>
{msg.choices.map((choice, i: number) => {
return (
<button
disabled={
clicked.includes(mi) ||
messages.length > mi + 2
}
className={
(_.some(selected, {
i: i,
mi: mi,
})
? "selected"
: "") +
(msg.choices?.length > 6 ||
(msg.choices?.length > 4 &&
msg.choices[0].title === "5" &&
msg.choices[4].title === "1" &&
!msg.choices[5])
? ""
: " full")
}
key={i}
onClick={() => {
handleSubmit(choice.title);
setClicked([...clicked, mi]);
setSelected([
...selected,
{
i: i,
mi: mi,
},
]);
}}
>
{msg.choices.length > 6 ||
(msg.choices.length > 4 &&
msg.choices[0].title === "5" &&
msg.choices[4].title === "1" &&
!msg.choices[5] && (
<StarIcon className="star" />
))}
<p>{choice.title}</p>
</button>
);
})}
</div>
</div>
)}
</>
</li>
);
}
})
) : (
<p>Chargement...</p>
))}
<div ref={messagesEndRef} />
</>
</ul>
)}
</div>
<form>
<LoadingBar
className="loading-bar"
color={"#47d3f6"}
progress={progress}
onLoaderFinished={() => setProgress(0)}
/>
<input
disabled={isVisible}
type="text"
value={input}
onChange={(e) => {
if (!authed) {
e.target.value = e.target.value
.replace(/^(\d\d)(\d)$/g, "$1/$2")
.replace(/^(\d\d\/\d\d)(\d+)$/g, "$1/$2")
.replace(/[^\d/]/g, "");
}
setInput(e.target.value);
}}
placeholder="Entrez votre message"
onFocus={() => scrollDown()}
size={!authed ? 10 : undefined}
maxLength={!authed ? 10 : 300}
/>
<button
className={input === "" ? "disabled" : ""}
onClick={(e) => {
e.preventDefault();
if (input !== "") {
setProgress(20);
handleSubmit(input);
if (!authed) {
setTransition(false);
setTimeout(() => {
setTransition(true);
scrollDown();
}, 100);
}
}
}}
>
<PaperAirplaneIcon />
</button>
</form>
<div
className={isVisible ? "botBar topped" : "botBar"}
onClick={() => handleState()}
>
CGU - ASISPO.COM @2022
</div>
{isVisible && (
<div
className={!closing ? "CGUcontainer" : "CGUcontainer disabled"}
{...fadeProps}
>
<CGU toggle={handleState} cgu={props.cgu} />
</div>
)}
</div>
)
);
};
export default Chat;
export async function getServerSideProps({ query }) {
const ihm_api: string = query.j ? process.env.IHM_API_J : process.env.IHM_API,
ihm_token: string = process.env.IHM_API_TOKEN;
let cgu: string = null;
let err = "none";
await fetch(ihm_api + "/a6po/api/common/get-cgu/", {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: ihm_token,
},
})
.then(async (res) => {
if (res.ok) {
return res.json();
} else {
throw await res.json();
}
})
.then(async (str) => {
if (str) {
cgu = await str.content;
}
})
.catch(() => {
err = "red";
});
return {
props: {
cgu: cgu,
err: err,
},
};
}