diff --git a/package-lock.json b/package-lock.json index f170d58..83f53b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -164,6 +164,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -567,6 +568,7 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -610,6 +612,7 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -1752,6 +1755,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.2.tgz", "integrity": "sha512-jwtMmJa1BXXDCiDx1vC6SFN/+HfYG53UkfJa6qeN5ogvOunzbFDO3wISZy5n9xgYFUrEP6M7e8EG++riHNTv9w==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/component": "0.6.18", "@firebase/logger": "0.4.4", @@ -1818,6 +1822,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.2.tgz", "integrity": "sha512-LssbyKHlwLeiV8GBATyOyjmHcMpX/tFjzRUCS1jnwGAew1VsBB4fJowyS5Ud5LdFbYpJeS+IQoC+RQxpK7eH3Q==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/app": "0.13.2", "@firebase/component": "0.6.18", @@ -1833,7 +1838,8 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@ethora/ai-chat-widget/node_modules/@firebase/auth-compat": { "version": "0.5.28", @@ -2284,6 +2290,7 @@ "integrity": "sha512-zGlBn/9Dnya5ta9bX/fgEoNC3Cp8s6h+uYPYaDieZsFOAdHP/ExzQ/eaDgxD3GOROdPkLKpvKY0iIzr9adle0w==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" }, @@ -2302,6 +2309,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz", "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2488,6 +2496,7 @@ "version": "0.13.1", "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.1.tgz", "integrity": "sha512-0O33PKrXLoIWkoOO5ByFaLjZehBctSYWnb+xJkIdx2SKP/K9l1UPFXPwASyrOIqyY3ws+7orF/1j7wI5EKzPYQ==", + "peer": true, "dependencies": { "@firebase/component": "0.6.17", "@firebase/logger": "0.4.4", @@ -2549,6 +2558,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.1.tgz", "integrity": "sha512-9VGjnY23Gc1XryoF/ABWtZVJYnaPOnjHM7dsqq9YALgKRtxI1FryvELUVkDaEIUf4In2bfkb9ZENF1S9M273Dw==", + "peer": true, "dependencies": { "@firebase/app": "0.13.1", "@firebase/component": "0.6.17", @@ -2563,7 +2573,8 @@ "node_modules/@ethora/chat-component/node_modules/@firebase/app-types": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", - "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==" + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "peer": true }, "node_modules/@ethora/chat-component/node_modules/@firebase/auth-compat": { "version": "0.5.27", @@ -2982,6 +2993,7 @@ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.0.tgz", "integrity": "sha512-Z4rK23xBCwgKDqmzGVMef+Vb4xso2j5Q8OG0vVL4m4fA5ZjPMYQazu8OJJC3vtQRC3SQ/Pgx/6TPNVsCd70QRw==", "hasInstallScript": true, + "peer": true, "dependencies": { "tslib": "^2.1.0" }, @@ -2998,6 +3010,7 @@ "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3206,6 +3219,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.13.tgz", "integrity": "sha512-OZiDAEK/lDB6xy/XzYAyJJkaDqmQ+BCtOEPLqFvxWKUz5JbBmej7IiiRHdtiIOD/twW7O5AxVsfaaGA/V1bNsA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/component": "0.6.9", "@firebase/logger": "0.4.2", @@ -3263,6 +3277,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.43.tgz", "integrity": "sha512-HM96ZyIblXjAC7TzE8wIk2QhHlSvksYkQ4Ukh1GmEenzkucSNUmUX4QvoKrqeWsLEQ8hdcojABeCV8ybVyZmeg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/app": "0.10.13", "@firebase/component": "0.6.9", @@ -3275,7 +3290,8 @@ "version": "0.9.2", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.2.tgz", "integrity": "sha512-oMEZ1TDlBz479lmABwWsWjzHwheQKiAgnuKxE0pz0IXCVx7/rtlkx1fQ6GfgK24WCrxDKMplZrT50Kh04iMbXQ==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@firebase/auth-compat": { "version": "0.5.14", @@ -3696,6 +3712,7 @@ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.0.tgz", "integrity": "sha512-xKtx4A668icQqoANRxyDLBLz51TAbDP9KRfpbKGxiCAW346d0BeJe5vN6/hKxxmWwnZ0mautyv39JxviwwQMOQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -4174,6 +4191,7 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.8.tgz", "integrity": "sha512-5S9UTjKZZBd9GfbcYh/nYfD9cv6OXmj5Y7NgKYfk7JcSoshp8/pW5zP4wecRiroBSZX8wcrywSgogpVNO+5W0Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.26.0", "@mui/core-downloads-tracker": "^6.4.8", @@ -5288,6 +5306,7 @@ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-4.10.0.tgz", "integrity": "sha512-KrMOL+sH69htCIXCaZ4JluJ35bchuCCznyPyrbN8JXSGQfwBI1SuIEMZNwvy8L8ykj29t6sa5BAAiL7fNoLZ8A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12.16" } @@ -5546,6 +5565,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.19.tgz", "integrity": "sha512-fcdJqaHOMDbiAwJnXv6XCzX0jDW77yI3tJqYh1Byn8EL5/S628WRx9b/y3DnNe55zTukUQKrfYxiZls2dHcUMw==", "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -5689,6 +5709,7 @@ "integrity": "sha512-XGwIabPallYipmcOk45DpsBSgLC64A0yvdAkrwEzwZ2viqGqRUJ8eEYoPz0CWnutgAFbNMPdsGGvzjSmcWVlEA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.27.0", "@typescript-eslint/types": "8.27.0", @@ -6224,6 +6245,7 @@ "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6569,6 +6591,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -7406,7 +7429,8 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz", "integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -7634,6 +7658,7 @@ "integrity": "sha512-9V/QURhsRN40xuHXWjV64yvrzMjcz7ZyNoF2jJFmy9j/SLk0u1OLSZgXi28MrXjymnjEGSR80WCdab3RGMDveQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -13267,6 +13292,7 @@ "version": "4.0.2", "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -13912,6 +13938,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", @@ -14212,6 +14239,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -14263,6 +14291,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -14610,13 +14639,15 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/redux-persist": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", "license": "MIT", + "peer": true, "peerDependencies": { "redux": ">4.0.0" } @@ -15737,6 +15768,7 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "license": "MIT", + "peer": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -15960,6 +15992,7 @@ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16336,6 +16369,7 @@ "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/src/assets/tutorial/background.png b/src/assets/tutorial/background.png new file mode 100644 index 0000000..abad418 Binary files /dev/null and b/src/assets/tutorial/background.png differ diff --git a/src/assets/tutorial/chat/chat_tutorial_one.png b/src/assets/tutorial/chat/chat_tutorial_one.png new file mode 100644 index 0000000..8969964 Binary files /dev/null and b/src/assets/tutorial/chat/chat_tutorial_one.png differ diff --git a/src/assets/tutorial/chat/chat_tutorial_three.png b/src/assets/tutorial/chat/chat_tutorial_three.png new file mode 100644 index 0000000..45d02d3 Binary files /dev/null and b/src/assets/tutorial/chat/chat_tutorial_three.png differ diff --git a/src/assets/tutorial/chat/chat_tutorial_two.png b/src/assets/tutorial/chat/chat_tutorial_two.png new file mode 100644 index 0000000..527028e Binary files /dev/null and b/src/assets/tutorial/chat/chat_tutorial_two.png differ diff --git a/src/assets/tutorial/tutorial-ai.png b/src/assets/tutorial/tutorial-ai.png new file mode 100644 index 0000000..7bab03d Binary files /dev/null and b/src/assets/tutorial/tutorial-ai.png differ diff --git a/src/assets/tutorial/tutorial-chat.png b/src/assets/tutorial/tutorial-chat.png new file mode 100644 index 0000000..0750df5 Binary files /dev/null and b/src/assets/tutorial/tutorial-chat.png differ diff --git a/src/assets/tutorial/tutorial-demo.png b/src/assets/tutorial/tutorial-demo.png new file mode 100644 index 0000000..1730d19 Binary files /dev/null and b/src/assets/tutorial/tutorial-demo.png differ diff --git a/src/assets/tutorial/video/appearance_chat_tutorial.mp4 b/src/assets/tutorial/video/appearance_chat_tutorial.mp4 new file mode 100644 index 0000000..f43fbdc Binary files /dev/null and b/src/assets/tutorial/video/appearance_chat_tutorial.mp4 differ diff --git a/src/assets/tutorial/welcome.png b/src/assets/tutorial/welcome.png new file mode 100644 index 0000000..2d6fe3c Binary files /dev/null and b/src/assets/tutorial/welcome.png differ diff --git a/src/assets/tutorial/widget/widget_tutorial_one.png b/src/assets/tutorial/widget/widget_tutorial_one.png new file mode 100644 index 0000000..7c576ae Binary files /dev/null and b/src/assets/tutorial/widget/widget_tutorial_one.png differ diff --git a/src/assets/tutorial/widget/widget_tutorial_one_answer.png b/src/assets/tutorial/widget/widget_tutorial_one_answer.png new file mode 100644 index 0000000..7b25901 Binary files /dev/null and b/src/assets/tutorial/widget/widget_tutorial_one_answer.png differ diff --git a/src/assets/tutorial/widget/widget_tutorial_three.png b/src/assets/tutorial/widget/widget_tutorial_three.png new file mode 100644 index 0000000..c5ae6b4 Binary files /dev/null and b/src/assets/tutorial/widget/widget_tutorial_three.png differ diff --git a/src/assets/tutorial/widget/widget_tutorial_two.png b/src/assets/tutorial/widget/widget_tutorial_two.png new file mode 100644 index 0000000..fd6f5d7 Binary files /dev/null and b/src/assets/tutorial/widget/widget_tutorial_two.png differ diff --git a/src/assets/tutorial/widget/widget_tutorial_two_answer.png b/src/assets/tutorial/widget/widget_tutorial_two_answer.png new file mode 100644 index 0000000..ea1e306 Binary files /dev/null and b/src/assets/tutorial/widget/widget_tutorial_two_answer.png differ diff --git a/src/components/modal/NewAppModal.tsx b/src/components/modal/NewAppModal.tsx index e3cf1ba..f841145 100644 --- a/src/components/modal/NewAppModal.tsx +++ b/src/components/modal/NewAppModal.tsx @@ -60,6 +60,9 @@ export function NewAppModal({ onClose, show }: Props) { navigate(`/app/admin/apps/${app._id}/settings`, { state: { from: location.pathname + location.search }, }); + localStorage.removeItem('newUser'); + localStorage.setItem('firstAdd', true.toString()); + onClose(); }, 1500); }) diff --git a/src/components/modal/PreviewAppModal.tsx b/src/components/modal/PreviewAppModal.tsx new file mode 100644 index 0000000..7139676 --- /dev/null +++ b/src/components/modal/PreviewAppModal.tsx @@ -0,0 +1,222 @@ +import { Dialog, DialogPanel } from '@headlessui/react'; +import { useEffect, useState } from 'react'; +import { IconClose } from '../Icons/IconClose'; + +import { CircularProgress } from '@mui/material'; +import classNames from 'classnames'; +import { SubmitHandler, useForm } from 'react-hook-form'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import { actionCreateApp } from '../../actions'; +import backgroundImage from '../../assets/tutorial/background.png'; +import welcomeImage from '../../assets/tutorial/welcome.png'; +import { useGoogleTranslateFix } from '../../hooks/useGoogleTranslateFix'; +// import { TextInput } from '../ui/TextInput'; + +interface Props { + onClose: () => void; + show: boolean; + haveApps: boolean; +} + +type Inputs = { + appName: string; +}; + +export function PreviewAppModal({ onClose, show }: Props) { + const navigate = useNavigate(); + const location = useLocation(); + const fixKey = useGoogleTranslateFix(); + + const [step, setStep] = useState(0); + const [loading, setLoading] = useState(false); + const [progress, setProgress] = useState(0); + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + const [dots, setDots] = useState(''); + + const onSubmit: SubmitHandler = ({ appName }) => { + setLoading(true); + setStep(2); + setProgress(0); + + let serverResponded = false; + + const interval = setInterval(() => { + setProgress((prev) => { + if (serverResponded) return prev; + return prev < 100 ? prev + 5 : 100; + }); + }, 500); + + actionCreateApp(appName) + .then((app) => { + serverResponded = true; + clearInterval(interval); + + setProgress(100); + + setTimeout(() => { + toast('Application created successfully!'); + setLoading(false); + navigate(`/app/admin/apps/${app._id}/settings`, { + state: { from: location.pathname + location.search }, + }); + localStorage.removeItem('newUser'); + localStorage.setItem('firstAdd', true.toString()); + + onClose(); + }, 1500); + }) + .catch(() => { + toast.error('Error creating application.'); + clearInterval(interval); + setLoading(false); + }); + }; + + useEffect(() => { + if (!loading) return; + + const dotsInterval = setInterval(() => { + setDots((prev) => (prev.length < 3 ? prev + '.' : '')); + }, 500); + + return () => clearInterval(dotsInterval); + }, [loading]); + + return ( + {}} + > + + + + {step === 0 && ( +
+ Welcome +
+

Welcome to Ethora

+
+ Thank you for joining! This is your admin panel. Here you can create Apps + for your projects. Also, you can manage various features such as Chats and AI bots. +
+
+ +
+
+
+ )} + + {step === 1 && ( + <> +
+ Create your first app! +
+

+ Create your first application and start leveraging the full + capabilities of app.ethora. +

+
+
+ + {errors.appName && ( +

+ {errors.appName.message} +

+ )} +
+
+ {/* */} + +
+
+ + )} + + {step == 2 && ( + <> +
+ Application creation in progress! +
+

+ Your app is being deployed. Please wait, this might take up to + 10-15 seconds{dots} +

+ +
+
+ +
+ {progress}% +
+
+
+ + )} +
+
+ ); +} diff --git a/src/components/modal/SettingsTutorialModal/DataTutorial.tsx b/src/components/modal/SettingsTutorialModal/DataTutorial.tsx index 8cf65a7..7d37946 100644 --- a/src/components/modal/SettingsTutorialModal/DataTutorial.tsx +++ b/src/components/modal/SettingsTutorialModal/DataTutorial.tsx @@ -1,34 +1,51 @@ -import ChatBubbleOutlineIcon from '@mui/icons-material/ChatBubbleOutline'; -import OndemandVideoOutlinedIcon from '@mui/icons-material/OndemandVideoOutlined'; -import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined'; import { NavLink } from 'react-router-dom'; import { QuestionsType } from './typeTutorial'; +import IconChat from '../../../assets/tutorial/tutorial-chat.png'; +import IconAi from '../../../assets/tutorial/tutorial-ai.png'; +import IconDemo from '../../../assets/tutorial/tutorial-demo.png'; + + +// Chat tutorial +import ChatTutorialOne from '../../../assets/tutorial/chat/chat_tutorial_one.png'; +import ChatTutorialTwo from '../../../assets/tutorial/chat/chat_tutorial_two.png'; +import ChatTutorialThree from '../../../assets/tutorial/chat/chat_tutorial_three.png'; + +//Chat video +import ChatVideoOne from '../../../assets/tutorial/video/appearance_chat_tutorial.mp4'; + +// AI tutorial +import AiTutorialOne from '../../../assets/tutorial/widget/widget_tutorial_one.png'; +import AiTutorialOneAnswer from '../../../assets/tutorial/widget/widget_tutorial_one_answer.png'; +import AiTutorialTwo from '../../../assets/tutorial/widget/widget_tutorial_two.png'; +import AiTutorialTwoAnswer from '../../../assets/tutorial/widget/widget_tutorial_two_answer.png'; +import AiTutorialThree from '../../../assets/tutorial/widget/widget_tutorial_three.png'; + export const stepsStartView: { title: string; description: string; - icon: React.ElementType; + icon: string; color: string; bgColor?: string; }[] = [ { title: 'Chat', - description: 'Talk to our assistant in real-time', - icon: ChatBubbleOutlineIcon, + description: 'Build or integrate instant messaging experience.', + icon: IconChat, color: 'text-purple-600', bgColor: 'bg-purple-50 hover:bg-purple-100', }, { title: 'AI', - description: 'Get AI-powered assistance', - icon: SmartToyOutlinedIcon, + description: 'Deploy AI agent for your visitors or your team.', + icon: IconAi, color: 'text-yellow-600', bgColor: 'bg-yellow-50 hover:bg-yellow-100', }, { title: 'Demo', - description: 'Explore a demo of our features', - icon: OndemandVideoOutlinedIcon, + description: 'Book a demo with Ethora team.', + icon: IconDemo, color: 'text-green-600', bgColor: 'bg-green-50 hover:bg-green-100', }, @@ -37,92 +54,56 @@ export const stepsStartView: { export const getQuestionsChat = (query?: string): QuestionsType => [ { id: 'one', - question: [ - 'quickly build a new ', - Chat app, - ' for ', - Web, - ' (using no code or low code if possible)', - ], + question: { + title: 'New web app', + time: `(no code, 5 min)`, + description: 'Launch your own web app with unique URL address, logo and colours without leaving the admin panel. Manage Chat rooms, on-board Users and AI agents if required.', + image: ChatTutorialOne, + link: `/app/admin/apps/${query}/settings?tab=Appearance` + }, answer: { time: `5–15 minutes`, complexity: 2, description: [ - - Go to the{' '} - - Appearance - {' '} - section, then customize the appearance of your application. - , - - Go to the{' '} - - Web - {' '} - , then enter the URL address of the application name and navigate to - it. - , + 'Launch your own web app with unique URL address, logo and colours without leaving the admin panel. Manage Chat rooms, on-board Users and AI agents if required.', ], - images: ['/src/assets/gif/appearance.gif', '/src/assets/gif/webApp.gif'], + // images: ['/src/assets/gif/appearance.gif'], + video: ChatVideoOne, }, }, { id: 'two', - question: [ - 'quickly build a new ', - Chat app, - ' for ', - iOS, - ' or ', - Android, - ' (using no code or low code if possible)', - ], + question: { + title: 'New iOS/Android React Native app', + time: `(low code, 30 min)`, + description: 'Build your own iOS or Android app using our open-source engine. Manage Chat rooms, on-board Users and AI agents if required.', + image: ChatTutorialTwo, + link: `/app/admin/apps/${query}/settings?tab=Mobile+App` + }, answer: { time: `5–15 minutes`, complexity: 2, description: [ - - Take user to{' '} - - Mobile - {' '} - &{' '} - - Appearance - {' '} - , then guide to test as End User - , + 'Build your own iOS or Android app using our open-source engine. Manage Chat rooms, on-board Users and AI agents if required.', ], + images: ['/src/assets/gif/webApp.gif'], }, }, { id: 'three', - question: [ - 'develop a new app ', - from scratch, - ' or ', - integrate chat screen, - ' into my React or Javascript app (using Ethora ', - SDK NPM, - ' component) leveraging Ethora Chat & AI infrastructure', - ], + question: { + title: 'Existing app integration', + time: `(days)`, + description: 'Add chat into your existing apps using Ethora SDK: NPM chat component, Swift SPM library, Javascript iframe widget, API, Chat Protocol and Bots Framework.', + image: ChatTutorialThree, + link: `https://github.com/dappros` + }, answer: { - time: `days to weeks`, - complexity: 4, - description: [Take user to documentation on NPM component], + time: `5–15 minutes`, + complexity: 2, + description: [ + 'Add chat into your existing apps using Ethora SDK: NPM chat component, Swift SPM library, Javascript iframe widget, API, Chat Protocol and Bots Framework.', + ], }, }, ]; @@ -130,50 +111,55 @@ export const getQuestionsChat = (query?: string): QuestionsType => [ export const getQuestionsAi = (query?: string): QuestionsType => [ { id: 'one', - question: [ - ..add AI, - ' to my no-code web/mobile ', - app built with Ethora, - ], + question: { + title: 'AI widget (copy & paste)', + time: `(low code, 10 min)`, + description: 'Get a Javascript to add AI agent into your website or test it right here in the admin panel. Index your website or upload documents to train your project specific AI agent.', + image: AiTutorialOne, + link: `/app/admin/apps/${query}/settings?tab=AI+Widget` + }, answer: { time: `5–15 minutes`, complexity: 2, description: [ - - Take user to{' '} - - Ai bot - {' '} - tab - , + 'Use the opportunity to give your AI widget a branded look — specify a convenient and memorable name, and set the path to a local avatar so the widget fits perfectly into your project’s design. After configuring everything, simply copy the generated code and paste it at the end of the tag — and your personalized AI assistant will be fully ready to work in your application or on your website. Fast, simple, and without any extra steps.', ], + images: [AiTutorialOneAnswer], }, }, { id: 'two', - question: ['..add AI to my Wordpress website (using a WP plugin)'], + question: { + title: 'AI widget (WP plugin)', + time: `(low code, 15 min)`, + description: 'Download and install our Wordpress plugin to launch AI agent for your website. Index your website or upload documents via admin panel to manage context.', + image: AiTutorialTwo, + link: `/app/admin/apps/${query}/settings?tab=AI+Widget` + }, answer: { - time: `0.5–1 hour`, + time: `5–15 minutes`, complexity: 2, description: [ - Take user to documentation on WP or WP plugin page, + 'Use this section to define your bot’s personality and interaction style. Here, you can describe in detail how the assistant should respond to users, what tasks it should perform, and what tone of communication it should maintain. If needed, you can also add important context about your business so the bot understands the specifics of your products and works as accurately and effectively as possible.', ], + images: [AiTutorialTwoAnswer], }, }, { id: 'three', - question: [ - '..add AI to my website, web app or enterprise web portal (using a ready widget code)', - ], + question: { + title: 'AI web app', + time: `(no code, 15 min)`, + description: 'Launch your own AI agent app with your unique URL, logo and branding. Index your website or upload documents to make your AI agent efficient for your use case.', + image: AiTutorialThree, + link: `/app/admin/apps/${query}/settings?tab=AI+Widget` + }, answer: { - time: `0.5–4 hours`, + time: `5–15 minutes`, complexity: 2, description: [ - TaTake user to{' '} + Take user to{' '} [ }, }, ]; - -export const getQuestionsDemo = (): QuestionsType => [ - { - id: 'one', - question: [ - 'Book a free feasibility call with Ethora team to help you choose the right tools and see examples in action.', - ], - answer: { - description: [[Book a feasibility call]], - }, - }, -]; diff --git a/src/components/modal/SettingsTutorialModal/SettingTutorialModal.tsx b/src/components/modal/SettingsTutorialModal/SettingTutorialModal.tsx index 57143e9..0745a7e 100644 --- a/src/components/modal/SettingsTutorialModal/SettingTutorialModal.tsx +++ b/src/components/modal/SettingsTutorialModal/SettingTutorialModal.tsx @@ -5,13 +5,13 @@ import { FC, ReactElement, useState } from 'react'; import { useParams } from 'react-router-dom'; import { TransitionGroup } from 'react-transition-group'; import { IconClose } from '../../Icons/IconClose'; -import { StepChooseTutorial, StepStartTutorial } from './components'; +import { StepChooseTutorial, StepStartTutorial, ListQuestion } from './components'; import { getQuestionsAi, getQuestionsChat, - getQuestionsDemo, } from './DataTutorial'; import { Step } from './typeTutorial'; +import { DemoComponentForm } from './components/DemoComponentForm'; interface SettingTutorialModalProps { show: boolean; @@ -24,11 +24,11 @@ export const SettingTutorialModal: FC = ({ }): ReactElement => { const [step, setStep] = useState('Start'); const [questionStep, setQuestionStep] = useState('default'); + const [selectedQuestionId] = useState(); const [animate, setAnimate] = useState(false); const { appId } = useParams(); const questionsChat = getQuestionsChat(appId); const questionsAi = getQuestionsAi(appId); - const questionsDemo = getQuestionsDemo(); const handleChangeStep = (next: Step) => { setAnimate(true); @@ -46,13 +46,52 @@ export const SettingTutorialModal: FC = ({ }, 200); }; - const goBack = () => handleChangeStep('Start'); + const goBack = () => { + if (step === 'ChatList' || step === 'AIList') { + handleChangeStep('Start'); + } else if (step === 'ChatQuestion' || step === 'AIQuestion') { + const listStep = step === 'ChatQuestion' ? 'ChatList' : 'AIList'; + handleChangeStep(listStep); + } else { + handleChangeStep('Start'); + } + }; const currentComponent = () => { switch (step) { case 'Start': - return ; - case 'Chat': + return { + if (selectedStep === 'Chat') { + handleChangeStep('ChatList'); + } else if (selectedStep === 'AI') { + handleChangeStep('AIList'); + } else { + handleChangeStep(selectedStep); + } + }} />; + case 'ChatList': + return ( + handleSelectQuestion(questionId, 'Chat')} + /> + ); + case 'AIList': + return ( + handleSelectQuestion(questionId, 'AI')} + /> + ); + case 'ChatQuestion': return ( = ({ handleChangeQuestionStep={handleChangeQuestionStep} questions={questionsChat} goBack={goBack} + navigateStart={`?tab=Appearance`} + onClose={onClose} + initialQuestionId={selectedQuestionId} /> ); - case 'AI': + case 'AIQuestion': return ( = ({ handleChangeQuestionStep={handleChangeQuestionStep} questions={questionsAi} goBack={goBack} + navigateStart={`?tab=AI+Widget`} + onClose={onClose} + initialQuestionId={selectedQuestionId} /> ); + case 'Chat': + case 'AI': + const listStep = step === 'Chat' ? 'ChatList' : 'AIList'; + handleChangeStep(listStep); + return null; case 'Demo': return ( - ); } @@ -93,10 +141,9 @@ export const SettingTutorialModal: FC = ({ > + ))} + + + + ) +} \ No newline at end of file diff --git a/src/components/modal/SettingsTutorialModal/components/QuestionsStepDefault.tsx b/src/components/modal/SettingsTutorialModal/components/QuestionsStepDefault.tsx index b6907ed..07d424e 100644 --- a/src/components/modal/SettingsTutorialModal/components/QuestionsStepDefault.tsx +++ b/src/components/modal/SettingsTutorialModal/components/QuestionsStepDefault.tsx @@ -30,9 +30,7 @@ export const QuestionsStepDefault = ({ >
- {question.map((part, i) => ( - {part} - ))} + {question.title}
diff --git a/src/components/modal/SettingsTutorialModal/components/StepChooseTutorial.tsx b/src/components/modal/SettingsTutorialModal/components/StepChooseTutorial.tsx index f956bf6..abf43f5 100644 --- a/src/components/modal/SettingsTutorialModal/components/StepChooseTutorial.tsx +++ b/src/components/modal/SettingsTutorialModal/components/StepChooseTutorial.tsx @@ -1,53 +1,220 @@ import clsx from 'clsx'; -import { ReactElement } from 'react'; -import { TransitionGroup } from 'react-transition-group'; -import { AnswerStep, QuestionsStepDefault } from '.'; +import { ReactElement, useState } from 'react'; +import { StepLayout } from '.'; import { QuestionsType } from '../typeTutorial'; +import { useNavigate } from 'react-router-dom'; + +import KeyboardArrowLeftRoundedIcon from '@mui/icons-material/KeyboardArrowLeftRounded'; +import KeyboardArrowRightRoundedIcon from '@mui/icons-material/KeyboardArrowRightRounded'; export const StepChooseTutorial = ({ questions, goBack, animate, - questionStep, - handleChangeQuestionStep, + navigateStart, + onClose, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + questionStep: _questionStep, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + handleChangeQuestionStep: _handleChangeQuestionStep, + initialQuestionId, }: { + navigateStart?: string; goBack: () => void; + onClose: () => void; questions: QuestionsType; animate: boolean; questionStep: string; handleChangeQuestionStep: (next: string) => void; + initialQuestionId?: string; }): ReactElement => { - const currentComponent = () => { - switch (questionStep) { - case 'default': - return ( - - ); - default: - return ( - - ); + const navigate = useNavigate(); + + // Находим индекс начального вопроса по ID, если он передан + const getInitialStep = () => { + if (initialQuestionId) { + const index = questions.findIndex(q => q.id === initialQuestionId); + return index >= 0 ? index : 0; + } + return 0; + }; + + const [currentStep, setCurrentStep] = useState(getInitialStep); + const [direction, setDirection] = useState<'left' | 'right'>('right'); + const totalSteps = questions.length; + + if (totalSteps === 0) { + return ( +
+

No questions available

+
+ ); + } + + const handleNext = () => { + if (currentStep < totalSteps - 1) { + setDirection('right'); + setCurrentStep(prev => prev + 1); + } + }; + + const handleBack = () => { + if (currentStep > 0) { + setDirection('left'); + setCurrentStep(prev => prev - 1); + } else { + goBack(); } }; + const handleStart = () => { + navigate(navigateStart || '/'); + onClose(); + }; + + + const currentQuestion = questions[currentStep]; + const isLastStep = currentStep === totalSteps - 1; + return ( - <> - - {currentComponent()} - - +
+ +
+ +
+ +
+ {currentQuestion.answer.description.map((desc, index) => ( +
+ + {currentQuestion.answer.images && currentQuestion.answer.images[index] && ( + Demo animation + )} + {currentQuestion.answer.video && ( + + )} +
+ {currentQuestion.question.title} +
+ +
+ {desc} +
+
+ ))} + + {/*
+ {currentQuestion.answer.time && ( +
+ Time: + {currentQuestion.answer.time} +
+ )} + + {currentQuestion.answer.complexity && ( +
+ Complexity: + +
+ )} +
*/} +
+
+
+ +
+
+ + +
+ {Array.from({ length: totalSteps }).map((_, index) => ( +
+ ))} +
+ + +
+
+ +
); }; diff --git a/src/components/modal/SettingsTutorialModal/components/StepStartTutorial.tsx b/src/components/modal/SettingsTutorialModal/components/StepStartTutorial.tsx index 41969c7..504183e 100644 --- a/src/components/modal/SettingsTutorialModal/components/StepStartTutorial.tsx +++ b/src/components/modal/SettingsTutorialModal/components/StepStartTutorial.tsx @@ -1,6 +1,5 @@ import classNames from 'classnames'; import { ReactElement } from 'react'; -import { StepLayout } from '.'; import { stepsStartView } from '../DataTutorial'; import { Step } from '../typeTutorial'; @@ -10,33 +9,58 @@ export const StepStartTutorial = ({ onSelect: (step: Step) => void; }): ReactElement => { return ( - +
+

+ Choose Your Path +

+

+ Select one of the three approaches to continue with your personalized experience. +

+
{stepsStartView.map( - ({ title, description, icon: Icon, color, bgColor }, index) => ( - -
- {title} - {description} +
+
+ {title} +
+ +
+ {description} +
- +
) )} - +
+
); }; diff --git a/src/components/modal/SettingsTutorialModal/components/index.ts b/src/components/modal/SettingsTutorialModal/components/index.ts index ea597a3..895b59e 100644 --- a/src/components/modal/SettingsTutorialModal/components/index.ts +++ b/src/components/modal/SettingsTutorialModal/components/index.ts @@ -3,3 +3,4 @@ export * from "./AnswerStep"; export * from "./QuestionsStepDefault"; export * from "./StepChooseTutorial"; export * from "./StepStartTutorial"; +export * from "./ListQuestion"; diff --git a/src/components/modal/SettingsTutorialModal/typeTutorial.ts b/src/components/modal/SettingsTutorialModal/typeTutorial.ts index 94a6c39..274e564 100644 --- a/src/components/modal/SettingsTutorialModal/typeTutorial.ts +++ b/src/components/modal/SettingsTutorialModal/typeTutorial.ts @@ -1,14 +1,21 @@ -export type Step = 'Start' | 'Chat' | 'AI' | 'Demo'; +export type Step = 'Start' | 'Chat' | 'AI' | 'Demo' | 'ChatList' | 'AIList' | 'ChatQuestion' | 'AIQuestion'; -type QuestionPart = string | React.ReactNode; +type QuestionPart = { + title: string; + time: string; + description: string; + image: string; + link: string; +}; export type QuestionsType = { id: string; - question: QuestionPart[]; + question: QuestionPart; answer: { time?: string; complexity?: number; description: Array; images?: Array; + video?: string; }; }[]; \ No newline at end of file diff --git a/src/pages/AdminApps.tsx b/src/pages/AdminApps.tsx index 98e081e..f3f2b65 100644 --- a/src/pages/AdminApps.tsx +++ b/src/pages/AdminApps.tsx @@ -1,4 +1,5 @@ import { AxiosError } from 'axios'; +import classNames from 'classnames'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useLocation, useSearchParams } from 'react-router-dom'; import { ApplicationPreview } from '../components/ApplicationPreview'; @@ -6,6 +7,7 @@ import { ApplicationStarterInf } from '../components/ApplicationStarterInf'; import { IconAdd } from '../components/Icons/IconAdd'; import { Loading } from '../components/Loading.tsx'; import { NewAppModal } from '../components/modal/NewAppModal'; +import { PreviewAppModal } from '../components/modal/PreviewAppModal.tsx'; import { Sorting } from '../components/Sorting'; import CsvButton from '../components/UI/Buttons/CSVButton.tsx'; import { Pagination } from '../components/UI/Pagination/Pagination.tsx'; @@ -13,13 +15,13 @@ import { useCentrifugeAppUpdater } from '../hooks/useCentrifugeAppUpdater.ts'; import { getExportAppsCsv, httpGetApps } from '../http'; import { ModelApp, OrderByType } from '../models'; import { useAppStore } from '../store/useAppStore'; -import classNames from 'classnames'; export default function AdminApps() { const [searchParams, setSearchParams] = useSearchParams(); const location = useLocation(); const [showStarterInf, setShowStarterInf] = useState(true); const [showModal, setShowModal] = useState(false); + const [newShowModal, setNewShowModal] = useState(false); const [loading, setLoading] = useState(false); const apps = useAppStore((s) => s.apps); @@ -163,9 +165,16 @@ export default function AdminApps() { }, [page]); useEffect(() => { + const newUser = localStorage.getItem('newUser'); + + if (newUser && !apps.length) { + return setNewShowModal(true); + } setShowModal(!apps.length); }, [apps.length]); + console.log('newShowModal', newShowModal); + useEffect(() => { fetchApps(); }, [fetchApps]); @@ -220,8 +229,8 @@ export default function AdminApps() {
)} diff --git a/src/pages/AppSettings/AppSettings.tsx b/src/pages/AppSettings/AppSettings.tsx index 4f67ff6..3e7b271 100644 --- a/src/pages/AppSettings/AppSettings.tsx +++ b/src/pages/AppSettings/AppSettings.tsx @@ -15,7 +15,6 @@ import { actionUpdateApp } from '../../actions'; import { IconExternalLink } from '../../components/Icons/IconExternalLink'; import { Loading } from '../../components/Loading'; import DeleteAppModal from '../../components/modal/DeleteAppModal'; -import InfoAppModal from '../../components/modal/InfoAppModal'; import TabApp from '../../components/TabApp'; import { deleteApp, @@ -42,6 +41,7 @@ import { MobileApp } from './MobileApp'; import { SignonOptions } from './SignonOptions'; import { Visibility } from './Visibility'; import { WebApp } from './WebApp'; +import { SettingTutorialModal } from '../../components/modal/SettingsTutorialModal/SettingTutorialModal'; const tabs = [ 'AI Widget', @@ -85,6 +85,14 @@ export default function AppSettings() { : 0; const [selectedIndex, setSelectedIndex] = useState(initialTabIndex); + const firstAdd = localStorage.getItem('firstAdd') === 'true'; + + useEffect(() => { + if (firstAdd) { + setIsInfo(true); + } + }, [firstAdd]); + useEffect(() => { if ( tabs.includes(tabFromUrl ?? '') && @@ -838,7 +846,7 @@ export default function AppSettings() { /> )} - {isInfo && ( + {/* {isInfo && ( - )} - {/* {isInfo && ( - setIsInfo(false)} /> )} */} + {isInfo && ( + setIsInfo(false)} /> + )} {loading && } diff --git a/src/pages/AuthPage/GoogleButton.tsx b/src/pages/AuthPage/GoogleButton.tsx index 17e3654..d129c1a 100644 --- a/src/pages/AuthPage/GoogleButton.tsx +++ b/src/pages/AuthPage/GoogleButton.tsx @@ -9,9 +9,9 @@ import { sendHSFormData, } from '../../http'; import { useAppStore } from '../../store/useAppStore'; +import { getUserCredsFromGoogle, IUser } from '../../utils/firebase'; import { navigateToUserPage } from '../../utils/navigateToUserPage'; import CustomButton from './Button'; -import { getUserCredsFromGoogle, IUser } from '../../utils/firebase'; import GoogleIcon from './Icons/socials/googleIcon'; interface GoogleButtonProps { @@ -26,7 +26,7 @@ export const GoogleButton = ({ utm }: GoogleButtonProps) => { try { const loginType = 'google'; let user, idToken, credential; - let creds: | { user: IUser; idToken: any; credential: any } | undefined; + let creds: { user: IUser; idToken: any; credential: any } | undefined; try { creds = await getUserCredsFromGoogle(); if (!creds) { @@ -40,7 +40,6 @@ export const GoogleButton = ({ utm }: GoogleButtonProps) => { console.error('here ', e); } - if (user) { if (!user.providerData[0].email) { toast.error('Email not provided by Google'); @@ -110,6 +109,8 @@ export const GoogleButton = ({ utm }: GoogleButtonProps) => { await actionAfterLogin(data); document.cookie = 'ethora_user=accregred; path=/; domain=.ethora.com; secure; samesite=lax; max-age=604800'; + localStorage.setItem('newUser', true.toString()); + navigateToUserPage(navigate, config?.afterLoginPage); }); } else { @@ -124,6 +125,8 @@ export const GoogleButton = ({ utm }: GoogleButtonProps) => { await actionAfterLogin(data); document.cookie = 'ethora_user=accregred; path=/; domain=.ethora.com; secure; samesite=lax; max-age=604800'; + localStorage.setItem('newUser', true.toString()); + navigateToUserPage(navigate, config?.afterLoginPage); }); } diff --git a/src/pages/AuthPage/Register/RegisterForm.tsx b/src/pages/AuthPage/Register/RegisterForm.tsx index e0df299..a00b64d 100644 --- a/src/pages/AuthPage/Register/RegisterForm.tsx +++ b/src/pages/AuthPage/Register/RegisterForm.tsx @@ -176,6 +176,7 @@ const RegisterForm: React.FC = ({ isSmallDevice = false }) => { .then(async ({ data }) => { document.cookie = 'ethora_user=1; path=/; domain=.ethora.com; secure; samesite=lax; max-age=604800'; + localStorage.setItem('newUser', true.toString()); await actionAfterLogin(data);