(SinglePayment.createRequestPost),
}
diff --git a/src/api/resources/Assistants.ts b/src/api/resources/Assistants.ts
new file mode 100644
index 0000000..1dcf735
--- /dev/null
+++ b/src/api/resources/Assistants.ts
@@ -0,0 +1,65 @@
+import routes from "@/routes";
+import { getAuthHeaders } from "../utils";
+
+export interface Payload {
+ token: string;
+}
+
+export interface Response {
+ ai_assistants: IAssistant[];
+}
+
+export interface IAssistant {
+ id: number;
+ name: string;
+ external_id: string;
+ external_chat_id: string | null;
+ photo: {
+ th: string;
+ th2x: string;
+ lg: string;
+ };
+ photo_mime_type: string;
+ created_at: string;
+ updated_at: string;
+ expirience: string;
+ rating: string;
+ stars: number;
+}
+
+export interface PayloadSetExternalChatId extends Payload {
+ chatId: string;
+ ai_assistant_chat: {
+ external_id: string;
+ };
+}
+
+export interface ResponseSetExternalChatId {
+ ai_assistant_chat: {
+ id: number;
+ assistant_id: number;
+ external_id: string;
+ created_at: string;
+ updated_at: string;
+ };
+}
+
+export const createRequest = ({ token }: Payload): Request => {
+ const url = new URL(routes.server.assistants());
+
+ return new Request(url, { method: "GET", headers: getAuthHeaders(token) });
+};
+
+export const createRequestSetExternalChatId = ({
+ token,
+ ai_assistant_chat,
+ chatId,
+}: PayloadSetExternalChatId) => {
+ const url = new URL(routes.server.setExternalChatIdAssistants(chatId));
+ const body = JSON.stringify({ ai_assistant_chat });
+ return new Request(url, {
+ method: "POST",
+ headers: getAuthHeaders(token),
+ body,
+ });
+};
diff --git a/src/api/resources/OpenAI.ts b/src/api/resources/OpenAI.ts
new file mode 100644
index 0000000..4697dc5
--- /dev/null
+++ b/src/api/resources/OpenAI.ts
@@ -0,0 +1,164 @@
+import { getAuthOpenAIHeaders } from "../utils";
+
+interface IDefaultPayload {
+ token: string;
+ method: "POST" | "GET";
+ path: string;
+}
+
+interface IMessagePayload {
+ role: string;
+ content: string;
+ file_ids?: string[];
+}
+
+export interface IMessage {
+ id: string;
+ object: "thread.message" | string;
+ created_at: number;
+ thread_id: string;
+ role: "user" | string;
+ content: IMessageContent[];
+ file_ids: unknown[];
+ assistant_id: null | unknown;
+ run_id: null | unknown;
+ metadata: object;
+}
+
+interface IMessageContent {
+ type: "text" | string;
+ text: {
+ value: string;
+ annotations: unknown[];
+ };
+}
+
+// Create thread
+export interface PayloadCreateThread extends IDefaultPayload {
+ messages: IMessagePayload[];
+}
+
+export interface ResponseCreateThread {
+ id: string;
+ object: string;
+ created_at: number;
+ metadata: object;
+}
+// Create thread end
+
+// Create message
+export interface PayloadCreateMessage extends IDefaultPayload {
+ role: "user";
+ content: string;
+}
+
+export type ResponseCreateMessage = IMessage;
+// Create message end
+
+// Get list messages
+export interface PayloadGetListMessages extends IDefaultPayload {
+ QueryParams?: {
+ limit?: number;
+ order?: "asc" | "desc";
+ after?: string;
+ before?: string;
+ };
+}
+
+export interface ResponseGetListMessages {
+ object: "list" | string;
+ data: IMessage[];
+ first_id: string;
+ last_id: string;
+ has_more: boolean;
+}
+// Get list messages end
+
+// Run thread
+interface ITool {
+ type: "code_interpreter" | string;
+}
+
+interface IUsage {
+ prompt_tokens: number;
+ completion_tokens: number;
+ total_tokens: number;
+}
+
+export interface PayloadRunThread extends IDefaultPayload {
+ assistant_id: string;
+}
+
+export interface ResponseRunThread {
+ id: string;
+ object: "thread.run" | string;
+ created_at: number;
+ assistant_id: string;
+ thread_id: string;
+ status: "queued" | string;
+ started_at: number;
+ expires_at: null | unknown;
+ cancelled_at: null | unknown;
+ failed_at: null | unknown;
+ completed_at: number;
+ last_error: null | unknown;
+ model: "gpt-4" | string;
+ instructions: null | unknown;
+ tools: ITool[];
+ file_ids: string[];
+ metadata: object;
+ usage: null | IUsage;
+}
+// Run thread end
+
+// Get status run thread
+export type PayloadGetStatusRunThread = IDefaultPayload;
+
+export type ResponseGetStatusRunThread = ResponseRunThread;
+// Get status run thread end
+
+// Get list runs
+export type PayloadGetListRuns = IDefaultPayload;
+
+export interface ResponseGetListRuns {
+ object: "list" | string;
+ data: ResponseRunThread[];
+ first_id: string;
+ last_id: string;
+ has_more: boolean;
+}
+// Get list runs end
+
+export type Payload =
+ | PayloadCreateThread
+ | PayloadCreateMessage
+ | PayloadGetListMessages
+ | PayloadRunThread
+ | PayloadGetStatusRunThread;
+
+export function createRequest({
+ token,
+ method,
+ path,
+ ...data
+}: Payload): Request {
+ const url = new URL(path);
+ const body = JSON.stringify({ ...data });
+ if ("QueryParams" in data && data.QueryParams) {
+ for (const [key, value] of Object.entries(data.QueryParams)) {
+ url.searchParams.set(key, String(value));
+ }
+ }
+ if (method === "GET") {
+ return new Request(url, {
+ method: method,
+ headers: getAuthOpenAIHeaders(token),
+ });
+ }
+
+ return new Request(url, {
+ method: method,
+ headers: getAuthOpenAIHeaders(token),
+ body,
+ });
+}
diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts
index bc355c7..6c11bf3 100644
--- a/src/api/resources/index.ts
+++ b/src/api/resources/index.ts
@@ -22,4 +22,6 @@ export * as GoogleAuth from "./GoogleAuth";
export * as SubscriptionPlans from "./SubscriptionPlans";
export * as AppleAuth from "./AppleAuth";
export * as AIRequestsV2 from "./AIRequestsV2";
+export * as Assistants from "./Assistants";
+export * as OpenAI from "./OpenAI";
export * as SinglePayment from "./SinglePayment";
diff --git a/src/api/utils.ts b/src/api/utils.ts
index 6464a96..39f80a1 100644
--- a/src/api/utils.ts
+++ b/src/api/utils.ts
@@ -1,34 +1,48 @@
-import { AuthToken } from './types'
-import { ErrorResponse, isErrorResponse, ApiError, buildUnknownError } from './errors'
+import { AuthToken } from "./types";
+import {
+ ErrorResponse,
+ isErrorResponse,
+ ApiError,
+ buildUnknownError,
+} from "./errors";
export function createMethod(createRequest: (payload: P) => Request) {
return async (payload: P): Promise => {
- const request = createRequest(payload)
- const response = await fetch(request)
- const data: R | ErrorResponse = await response.json()
- const statusCode = response.status
+ const request = createRequest(payload);
+ const response = await fetch(request);
+ const data: R | ErrorResponse = await response.json();
+ const statusCode = response.status;
if (!response.ok) {
- const body = isErrorResponse(data) ? data : { error: buildUnknownError(statusCode) }
- throw new ApiError({ body, statusCode })
+ const body = isErrorResponse(data)
+ ? data
+ : { error: buildUnknownError(statusCode) };
+ throw new ApiError({ body, statusCode });
}
if (isErrorResponse(data)) {
- throw new ApiError({ body: data, statusCode })
+ throw new ApiError({ body: data, statusCode });
}
- return data
- }
+ return data;
+ };
}
export function getBaseHeaders(): Headers {
return new Headers({
- 'Content-Type': 'application/json',
- })
+ "Content-Type": "application/json",
+ });
}
export function getAuthHeaders(token: AuthToken): Headers {
- const headers = getBaseHeaders()
- headers.append('Authorization', `Bearer ${token}`)
- return headers
+ const headers = getBaseHeaders();
+ headers.append("Authorization", `Bearer ${token}`);
+ return headers;
+}
+
+export function getAuthOpenAIHeaders(token: AuthToken): Headers {
+ const headers = getBaseHeaders();
+ headers.append("Authorization", `Bearer ${token}`);
+ headers.append("OpenAI-Beta", "assistants=v1");
+ return headers;
}
diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx
index 97356c3..7713ecb 100755
--- a/src/components/App/index.tsx
+++ b/src/components/App/index.tsx
@@ -104,6 +104,8 @@ import AddReportPage from "../pages/AdditionalPurchases/pages/AddReport";
import UnlimitedReadingsPage from "../pages/AdditionalPurchases/pages/UnlimitedReadings";
import AddConsultationPage from "../pages/AdditionalPurchases/pages/AddConsultation";
import StepsManager from "@/components/palmistry/steps-manager/steps-manager";
+import Advisors from "../pages/Advisors";
+import AdvisorChatPage from "../pages/AdvisorChat";
import PaymentWithEmailPage from "../pages/PaymentWithEmailPage";
import SuccessPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/SuccessPaymentPage";
import FailPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/FailPaymentPage";
@@ -125,6 +127,15 @@ function App(): JSX.Element {
const [searchParams] = useSearchParams();
const jwtToken = searchParams.get("token");
+ useEffect(() => {
+ // api.getAppConfig({ bundleId: "auraweb" }),
+ dispatch(
+ actions.siteConfig.update({
+ openAiToken: "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",
+ })
+ );
+ }, [dispatch]);
+
useEffect(() => {
if (!isProduction) return;
ReactGA.send({
@@ -453,6 +464,10 @@ function App(): JSX.Element {
path={routes.client.wallpaper()}
element={}
/>
+ } />
+
+ } />
+
}
@@ -578,6 +593,13 @@ function Layout({ setIsSpecialOfferOpen }: LayoutProps): JSX.Element {
image: "Compatibility.svg",
onClick: handleCompatibility,
},
+ {
+ title: "Advisors",
+ path: routes.client.advisors(),
+ paths: [routes.client.advisors()],
+ image: "moon.svg",
+ onClick: () => null,
+ },
{
title: "My Moon",
path: routes.client.wallpaper(),
diff --git a/src/components/BreathPage/index.tsx b/src/components/BreathPage/index.tsx
index ca23057..d18d01f 100644
--- a/src/components/BreathPage/index.tsx
+++ b/src/components/BreathPage/index.tsx
@@ -29,6 +29,15 @@ function BreathPage({ leoApng }: BreathPageProps): JSX.Element {
const navigate = useNavigate();
const homeConfig = useSelector(selectors.selectHome);
const showNavbarFooter = homeConfig.isShowNavbar;
+ const queryTimeOutRef = useRef();
+
+ useEffect(() => {
+ return () => {
+ if (queryTimeOutRef.current) {
+ clearTimeout(queryTimeOutRef.current);
+ }
+ };
+ }, []);
useEffect(() => {
if (isOpenModal) return;
@@ -67,8 +76,7 @@ function BreathPage({ leoApng }: BreathPageProps): JSX.Element {
setIsOpenModal(false);
};
-
- const token = useSelector(selectors.selectToken)
+ const token = useSelector(selectors.selectToken);
const createCallback = useCallback(async () => {
const data: UserCallbacks.PayloadPost = {
data: {
@@ -87,7 +95,7 @@ function BreathPage({ leoApng }: BreathPageProps): JSX.Element {
token,
});
if (!getCallback.user_callback.is_complete) {
- setTimeout(getUserCallbacksRequest, 3000);
+ queryTimeOutRef.current = setTimeout(getUserCallbacksRequest, 3000);
}
dispatch(
actions.userCallbacks.update({
diff --git a/src/components/CompatResultPage/index.tsx b/src/components/CompatResultPage/index.tsx
index abe7088..5bd1588 100644
--- a/src/components/CompatResultPage/index.tsx
+++ b/src/components/CompatResultPage/index.tsx
@@ -3,7 +3,7 @@ import Title from "../Title";
import styles from "./styles.module.css";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
-import { useCallback, useState } from "react";
+import { useCallback, useEffect, useRef, useState } from "react";
import { AICompats, AIRequests, useApi, useApiCall } from "@/api";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
@@ -26,6 +26,15 @@ function CompatResultPage(): JSX.Element {
const [isOpenModal, setIsOpenModal] = useState(true);
const [isVisualLoading, setIsVisualLoading] = useState(true);
const [isDataLoading, setIsDataLoading] = useState(true);
+ const queryTimeOutRef = useRef();
+
+ useEffect(() => {
+ return () => {
+ if (queryTimeOutRef.current) {
+ clearTimeout(queryTimeOutRef.current);
+ }
+ };
+ }, []);
const handleNext = () => {
if (homeConfig.pathFromHome === EPathsFromHome.breath) {
@@ -67,7 +76,7 @@ function CompatResultPage(): JSX.Element {
token,
});
if (aIRequest.ai_request.state !== "ready") {
- setTimeout(loadAIRequest, 3000);
+ queryTimeOutRef.current = setTimeout(loadAIRequest, 3000);
}
setText(aIRequest?.ai_request?.response?.body || "Loading...");
setIsDataLoading(false);
diff --git a/src/components/Loader/index.tsx b/src/components/Loader/index.tsx
index 034bbaa..bf10f33 100644
--- a/src/components/Loader/index.tsx
+++ b/src/components/Loader/index.tsx
@@ -1,21 +1,30 @@
-import './styles.css'
+import "./styles.css";
export enum LoaderColor {
- White = 'white',
- Black = 'black',
+ White = "white",
+ Black = "black",
+ Red = "red",
}
type LoaderProps = {
- color?: LoaderColor
-}
+ color?: LoaderColor;
+ className?: string;
+};
-function Loader({ color = LoaderColor.Black }: LoaderProps): JSX.Element {
- const colorClass = color === LoaderColor.White ? 'loader__white' : 'loader__black'
+const colorClasses = {
+ [LoaderColor.White]: "loader__white",
+ [LoaderColor.Black]: "loader__black",
+ [LoaderColor.Red]: "loader__red",
+};
+
+function Loader({ color = LoaderColor.Black, className }: LoaderProps): JSX.Element {
return (
-
-
+
- )
+ );
}
-export default Loader
+export default Loader;
diff --git a/src/components/Loader/styles.css b/src/components/Loader/styles.css
index 476e0a7..7b33eb6 100644
--- a/src/components/Loader/styles.css
+++ b/src/components/Loader/styles.css
@@ -26,6 +26,10 @@
border-color: #fff;
}
+.loader.loader__red span::after {
+ border-color: #ff2c57;
+}
+
@keyframes loader-1-1 {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
diff --git a/src/components/NavbarFooter/index.tsx b/src/components/NavbarFooter/index.tsx
index 95c65dc..974c84e 100644
--- a/src/components/NavbarFooter/index.tsx
+++ b/src/components/NavbarFooter/index.tsx
@@ -29,7 +29,7 @@ function NavbarFooter({ items }: INavbarHomeProps): JSX.Element {
const [isShowOnboardingNavbarFooter, setIsShowOnboardingNavbarFooter] =
useState(!onboardingConfigNavbarFooter?.isShown);
- const [selectedOnboarding, setSelectedOnboarding] = useState(3);
+ const [selectedOnboarding, setSelectedOnboarding] = useState(4);
const [rerender, setRerender] = useState(false);
rerender
@@ -66,6 +66,12 @@ function NavbarFooter({ items }: INavbarHomeProps): JSX.Element {
onClick: () => setShownOnboarding(),
classNameText: `${styles["compatibility-onboarding__text"]} ${styles.onboarding}`,
},
+ // {
+ // text: t("au.web_onbording.moon"),
+ // onClick: () => setSelectedOnboarding(0),
+ // classNameText: `${styles["moon-onboarding__text"]} ${styles.onboarding}`,
+ // },
+ null,
{
text: t("au.web_onbording.moon"),
onClick: () => setSelectedOnboarding(0),
@@ -74,7 +80,7 @@ function NavbarFooter({ items }: INavbarHomeProps): JSX.Element {
];
const handleClick = (item: INavbarHomeItems) => {
- onboardingsSettings[selectedOnboarding].onClick();
+ onboardingsSettings[selectedOnboarding]?.onClick();
if (item.onClick) {
item.onClick();
}
@@ -96,9 +102,9 @@ function NavbarFooter({ items }: INavbarHomeProps): JSX.Element {
isShow={index === selectedOnboarding}
>
onboardingsSettings[index].onClick()}
- classNameText={onboardingsSettings[index].classNameText}
+ text={onboardingsSettings[index]?.text || ""}
+ crossClickHandler={() => onboardingsSettings[index]?.onClick()}
+ classNameText={onboardingsSettings[index]?.classNameText}
/>
)}
diff --git a/src/components/UserCallbacksPage/styles.module.css b/src/components/UserCallbacksPage/styles.module.css
index 73abc19..1bc8293 100644
--- a/src/components/UserCallbacksPage/styles.module.css
+++ b/src/components/UserCallbacksPage/styles.module.css
@@ -26,6 +26,7 @@
.result-container__value-label {
color: #fff;
font-weight: 500;
+ text-align: left;
}
.result-container__value-value {
diff --git a/src/components/pages/AdvisorChat/components/ChatHeader/index.tsx b/src/components/pages/AdvisorChat/components/ChatHeader/index.tsx
new file mode 100644
index 0000000..f9d31b7
--- /dev/null
+++ b/src/components/pages/AdvisorChat/components/ChatHeader/index.tsx
@@ -0,0 +1,30 @@
+import styles from "./styles.module.css";
+
+interface IChatHeaderProps {
+ name: string;
+ avatar: string;
+ classNameContainer?: string;
+ clickBackButton: () => void;
+}
+
+function ChatHeader({
+ name,
+ avatar,
+ classNameContainer = "",
+ clickBackButton,
+}: IChatHeaderProps) {
+ return (
+
+
+
+ {name}
+
+
+

+
+ );
+}
+
+export default ChatHeader;
diff --git a/src/components/pages/AdvisorChat/components/ChatHeader/styles.module.css b/src/components/pages/AdvisorChat/components/ChatHeader/styles.module.css
new file mode 100644
index 0000000..7097cf2
--- /dev/null
+++ b/src/components/pages/AdvisorChat/components/ChatHeader/styles.module.css
@@ -0,0 +1,71 @@
+.container {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ background: rgb(255 255 255 / 10%);
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ padding: 2px 12px;
+}
+
+.avatar {
+ width: 42px;
+ height: 42px;
+ border-radius: 100%;
+}
+
+.online-status {
+ display: block;
+ width: 9px;
+ height: 9px;
+ background-color: rgb(6, 183, 6);
+ border-radius: 50%;
+ margin-top: 4px;
+}
+
+.name {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-weight: 700;
+}
+
+.back-button {
+ font-weight: 500;
+ color: rgb(0, 128, 255);
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 6px;
+ cursor: pointer;
+}
+
+.back-button > .arrow {
+ display: flex;
+ width: 14px;
+ height: 20px;
+ position: relative;
+}
+
+.back-button > .arrow::before,
+.back-button > .arrow::after {
+ content: "";
+ display: block;
+ width: 12px;
+ height: 2px;
+ background-color: rgb(0, 128, 255);
+ position: absolute;
+ left: 0;
+}
+
+.back-button > .arrow::before {
+ transform: rotate(-45deg);
+ top: calc(50% - 4px);
+}
+
+.back-button > .arrow::after {
+ transform: rotate(45deg);
+ top: calc(50% + 4px);
+}
diff --git a/src/components/pages/AdvisorChat/components/InputMessage/index.tsx b/src/components/pages/AdvisorChat/components/InputMessage/index.tsx
new file mode 100644
index 0000000..4aaed23
--- /dev/null
+++ b/src/components/pages/AdvisorChat/components/InputMessage/index.tsx
@@ -0,0 +1,56 @@
+import { ChangeEvent, FormEvent } from "react";
+import styles from "./styles.module.css";
+import Loader, { LoaderColor } from "@/components/Loader";
+
+interface IInputMessageProps {
+ placeholder?: string;
+ classNameContainer?: string;
+ messageText: string;
+ textareaRows: number;
+ disabledButton: boolean;
+ disabledTextArea: boolean;
+ isLoading: boolean;
+ handleChangeMessageText: (e: ChangeEvent) => void;
+ submitForm: (messageText: string) => void;
+}
+
+function InputMessage({
+ placeholder,
+ messageText,
+ textareaRows,
+ disabledButton,
+ disabledTextArea,
+ isLoading,
+ handleChangeMessageText,
+ submitForm,
+ classNameContainer = "",
+}: IInputMessageProps) {
+ const handleSubmit = (e: FormEvent) => {
+ e.preventDefault();
+ submitForm(messageText);
+ };
+
+ return (
+
+ );
+}
+
+export default InputMessage;
diff --git a/src/components/pages/AdvisorChat/components/InputMessage/styles.module.css b/src/components/pages/AdvisorChat/components/InputMessage/styles.module.css
new file mode 100644
index 0000000..2283dee
--- /dev/null
+++ b/src/components/pages/AdvisorChat/components/InputMessage/styles.module.css
@@ -0,0 +1,72 @@
+.container {
+ display: grid;
+ grid-template-columns: 1fr 36px;
+ align-items: center;
+ width: 100%;
+ gap: 8px;
+ padding: 8px 12px;
+}
+
+.button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 36px;
+ width: 36px;
+ min-width: 0;
+ /* aspect-ratio: 1 / 1; */
+ background-color: rgb(0, 110, 255);
+ border: none;
+ border-radius: 100%;
+}
+
+button:disabled {
+ opacity: 0.5;
+}
+
+.button > .arrow {
+ display: block;
+ position: absolute;
+ width: 2px;
+ height: 10px;
+ background-color: #fff;
+}
+
+.button > .arrow::before,
+.button > .arrow::after {
+ content: "";
+ display: block;
+ width: 6px;
+ height: 2px;
+ background-color: #fff;
+ position: absolute;
+ top: 0;
+}
+
+.button > .arrow::before {
+ transform: rotate(-45deg);
+ left: -4px;
+}
+
+.button > .arrow::after {
+ transform: rotate(45deg);
+}
+
+.input {
+ border: solid 1px #929292;
+ outline: none;
+ background-color: #232322;
+ color: #fff;
+ font-size: 18px;
+ padding: 4px 14px;
+ border-radius: 16px;
+ height: inherit;
+ min-height: 34px;
+ /* max-height: 176px; */
+ /* width: calc(100% - 34px); */
+ resize: none;
+}
+
+.input:disabled {
+ opacity: 0.5;
+}
diff --git a/src/components/pages/AdvisorChat/components/LoaderDots/index.tsx b/src/components/pages/AdvisorChat/components/LoaderDots/index.tsx
new file mode 100644
index 0000000..06f4d23
--- /dev/null
+++ b/src/components/pages/AdvisorChat/components/LoaderDots/index.tsx
@@ -0,0 +1,9 @@
+import styles from "./styles.module.css"
+
+function LoaderDots() {
+ return (
+
+ )
+}
+
+export default LoaderDots
\ No newline at end of file
diff --git a/src/components/pages/AdvisorChat/components/LoaderDots/styles.module.css b/src/components/pages/AdvisorChat/components/LoaderDots/styles.module.css
new file mode 100644
index 0000000..14f904b
--- /dev/null
+++ b/src/components/pages/AdvisorChat/components/LoaderDots/styles.module.css
@@ -0,0 +1,72 @@
+.container {
+ position: relative;
+ left: -9999px;
+ width: 10px;
+ height: 10px;
+ border-radius: 5px;
+ background-color: #7f7f7f;
+ color: #7f7f7f;
+ box-shadow: 9999px 0 0 -5px;
+ animation: container 1.5s infinite linear;
+ animation-delay: 0.25s;
+ margin: 2px 24px;
+}
+.container::before,
+.container::after {
+ content: "";
+ display: inline-block;
+ position: absolute;
+ top: 0;
+ width: 10px;
+ height: 10px;
+ border-radius: 5px;
+ background-color: #7f7f7f;
+ color: #7f7f7f;
+}
+.container::before {
+ box-shadow: 9984px 0 0 -5px;
+ animation: container-before 1.5s infinite linear;
+ animation-delay: 0s;
+}
+.container::after {
+ box-shadow: 10014px 0 0 -5px;
+ animation: container-after 1.5s infinite linear;
+ animation-delay: 0.5s;
+}
+
+@keyframes container-before {
+ 0% {
+ box-shadow: 9984px 0 0 -5px;
+ }
+ 30% {
+ box-shadow: 9984px 0 0 2px;
+ }
+ 60%,
+ 100% {
+ box-shadow: 9984px 0 0 -5px;
+ }
+}
+@keyframes container {
+ 0% {
+ box-shadow: 9999px 0 0 -5px;
+ }
+ 30% {
+ box-shadow: 9999px 0 0 2px;
+ }
+ 60%,
+ 100% {
+ box-shadow: 9999px 0 0 -5px;
+ }
+}
+@keyframes container-after {
+ 0% {
+ box-shadow: 10014px 0 0 -5px;
+ }
+ 30% {
+ box-shadow: 10014px 0 0 2px;
+ }
+ 60%,
+ 100% {
+ box-shadow: 10014px 0 0 -5px;
+ }
+}
diff --git a/src/components/pages/AdvisorChat/components/Message/index.tsx b/src/components/pages/AdvisorChat/components/Message/index.tsx
new file mode 100644
index 0000000..d7055e6
--- /dev/null
+++ b/src/components/pages/AdvisorChat/components/Message/index.tsx
@@ -0,0 +1,45 @@
+import React from "react";
+import styles from "./styles.module.css";
+
+interface IMessageProps {
+ text: string | React.ReactNode;
+ isSelf: boolean;
+ advisorName?: string;
+ avatar?: string;
+ backgroundTextColor?: string;
+ textColor?: string;
+}
+
+// eslint-disable-next-line react-refresh/only-export-components
+function Message({
+ text,
+ avatar,
+ isSelf,
+ advisorName = "Advisor",
+ backgroundTextColor = "#0080ff",
+ textColor = "#fff",
+}: IMessageProps) {
+ return (
+
+ {!isSelf && avatar?.length && (
+

+ )}
+
+ {text}
+
+
+ );
+}
+
+// eslint-disable-next-line react-refresh/only-export-components
+export default React.memo(Message);
diff --git a/src/components/pages/AdvisorChat/components/Message/styles.module.css b/src/components/pages/AdvisorChat/components/Message/styles.module.css
new file mode 100644
index 0000000..3712b4c
--- /dev/null
+++ b/src/components/pages/AdvisorChat/components/Message/styles.module.css
@@ -0,0 +1,21 @@
+.container {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ align-items: flex-end;
+ gap: 8px;
+}
+
+.avatar {
+ width: 36px;
+ height: 36px;
+ border-radius: 100%;
+ object-fit: contain;
+}
+
+.text {
+ width: fit-content;
+ max-width: 70%;
+ padding: 8px;
+ border-radius: 8px;
+}
diff --git a/src/components/pages/AdvisorChat/index.tsx b/src/components/pages/AdvisorChat/index.tsx
new file mode 100644
index 0000000..297ce63
--- /dev/null
+++ b/src/components/pages/AdvisorChat/index.tsx
@@ -0,0 +1,362 @@
+import { useNavigate, useParams } from "react-router-dom";
+import styles from "./styles.module.css";
+import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react";
+import { Assistants, useApi, useApiCall } from "@/api";
+import { useSelector } from "react-redux";
+import { selectors } from "@/store";
+import { IAssistant } from "@/api/resources/Assistants";
+import Loader, { LoaderColor } from "@/components/Loader";
+import ChatHeader from "./components/ChatHeader";
+import InputMessage from "./components/InputMessage";
+import routes from "@/routes";
+import { IMessage, ResponseRunThread } from "@/api/resources/OpenAI";
+import Message from "./components/Message";
+import LoaderDots from "./components/LoaderDots";
+import useDetectScroll from "@smakss/react-scroll-direction";
+
+function AdvisorChatPage() {
+ const { id } = useParams();
+ const api = useApi();
+ const navigate = useNavigate();
+ const { scrollDir, scrollPosition } = useDetectScroll();
+ const token = useSelector(selectors.selectToken);
+ const openAiToken = useSelector(selectors.selectOpenAiToken);
+ const [assistant, setAssistant] = useState();
+ const [messageText, setMessageText] = useState("");
+ const [textareaRows, setTextareaRows] = useState(1);
+ const [messages, setMessages] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+ const [isLoadingSelfMessage, setIsLoadingSelfMessage] = useState(false);
+ const [isLoadingAdvisorMessage, setIsLoadingAdvisorMessage] = useState(false);
+ const [isLoadingLatestMessages, setIsLoadingLatestMessages] = useState(false);
+ const timeOutStatusRunRef = useRef();
+ const messagesEndRef = useRef(null);
+ const [hasMoreLatestMessages, setHasMoreLatestMessages] = useState(true);
+
+ useEffect(() => {
+ if (
+ scrollDir === "up" &&
+ scrollPosition.top < 50 &&
+ !isLoadingLatestMessages
+ ) {
+ loadLatestMessages();
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [scrollDir, scrollPosition]);
+
+ const loadLatestMessages = async () => {
+ const lastIdMessage = messages[messages.length - 1]?.id;
+ if (!hasMoreLatestMessages || !lastIdMessage) {
+ return;
+ }
+ setIsLoadingLatestMessages(true);
+ const listMessages = await api.getListMessages({
+ token: openAiToken,
+ method: "GET",
+ path: routes.openAi.getListMessages(assistant?.external_chat_id || ""),
+ QueryParams: {
+ after: lastIdMessage,
+ limit: 20,
+ },
+ });
+ setHasMoreLatestMessages(listMessages.has_more);
+ setMessages((prev) => [...prev, ...listMessages.data]);
+ setIsLoadingLatestMessages(false);
+ };
+
+ const scrollToBottom = () => {
+ setTimeout(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
+ }, 100);
+ };
+
+ useEffect(() => {
+ return () => {
+ if (timeOutStatusRunRef.current) {
+ clearTimeout(timeOutStatusRunRef.current);
+ }
+ };
+ }, []);
+
+ const getCurrentAssistant = (
+ aiAssistants: Assistants.IAssistant[],
+ idAssistant: string | number
+ ) => {
+ const currentAssistant = aiAssistants.find(
+ (a) => a.id === Number(idAssistant)
+ );
+ return currentAssistant;
+ };
+
+ const updateMessages = async (threadId: string) => {
+ const listMessages = await api.getListMessages({
+ token: openAiToken,
+ method: "GET",
+ path: routes.openAi.getListMessages(threadId),
+ });
+ setMessages(listMessages.data);
+ scrollToBottom();
+ };
+
+ const setExternalChatIdAssistant = async (threadId: string) => {
+ await api.setExternalChatIdAssistant({
+ token,
+ chatId: String(id),
+ ai_assistant_chat: {
+ external_id: threadId,
+ },
+ });
+ };
+
+ const updateCurrentAssistant = async () => {
+ const { ai_assistants } = await api.assistants({
+ token,
+ });
+ const currentAssistant = getCurrentAssistant(ai_assistants, id || "");
+ setAssistant(currentAssistant);
+ return {
+ ai_assistants,
+ currentAssistant,
+ };
+ };
+
+ const loadData = useCallback(async () => {
+ const { ai_assistants, currentAssistant } = await updateCurrentAssistant();
+ let listRuns: ResponseRunThread[] = [];
+ if (currentAssistant?.external_chat_id?.length) {
+ await updateMessages(currentAssistant.external_chat_id);
+ const result = await api.getListRuns({
+ token: openAiToken,
+ method: "GET",
+ path: routes.openAi.getListRuns(currentAssistant.external_chat_id),
+ });
+ listRuns = result.data;
+ }
+
+ setIsLoading(false);
+ const runInProgress = listRuns.find((r) => r.status === "in_progress");
+
+ setTimeout(() => {
+ scrollToBottom();
+ });
+
+ if (runInProgress) {
+ setIsLoadingAdvisorMessage(true);
+
+ await checkStatusAndGetLastMessage(
+ runInProgress.thread_id,
+ runInProgress.id
+ );
+ setIsLoadingAdvisorMessage(false);
+ }
+ return { ai_assistants };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [api, id, openAiToken, token]);
+
+ useApiCall(loadData);
+
+ const createThread = async (messageText: string) => {
+ const thread = await api.createThread({
+ token: openAiToken,
+ method: "POST",
+ path: routes.openAi.createThread(),
+ messages: [
+ {
+ role: "user",
+ content: messageText,
+ },
+ ],
+ });
+ await setExternalChatIdAssistant(thread.id);
+ await updateCurrentAssistant();
+ return thread;
+ };
+
+ const getStatusThreadRun = async (
+ threadId: string,
+ runId: string
+ ): Promise => {
+ await new Promise(
+ (resolve) => (timeOutStatusRunRef.current = setTimeout(resolve, 1500))
+ );
+ const run = await api.getStatusRunThread({
+ token: openAiToken,
+ method: "GET",
+ path: routes.openAi.getStatusRunThread(threadId, runId),
+ assistant_id: `${assistant?.external_id}`,
+ });
+ if (run.status !== "completed") {
+ return await getStatusThreadRun(threadId, runId);
+ }
+ return run;
+ };
+
+ const createMessage = async (messageText: string, threadId: string) => {
+ const message = await api.createMessage({
+ token: openAiToken,
+ method: "POST",
+ path: routes.openAi.createMessage(threadId),
+ role: "user",
+ content: messageText,
+ });
+ return message;
+ };
+
+ const runThread = async (threadId: string, assistantId: string) => {
+ const run = await api.runThread({
+ token: openAiToken,
+ method: "POST",
+ path: routes.openAi.runThread(threadId),
+ assistant_id: assistantId,
+ });
+ return run;
+ };
+
+ const checkStatusAndGetLastMessage = async (
+ threadId: string,
+ runId: string
+ ) => {
+ const { status } = await getStatusThreadRun(threadId, runId);
+
+ if (status === "completed") {
+ await getLastMessage(threadId);
+ }
+ };
+
+ const getLastMessage = async (threadId: string) => {
+ const lastMessage = await api.getListMessages({
+ token: openAiToken,
+ method: "GET",
+ path: routes.openAi.getListMessages(threadId),
+ QueryParams: {
+ limit: 1,
+ },
+ });
+ setMessages((prev) => [lastMessage.data[0], ...prev]);
+ };
+
+ const sendMessage = async (messageText: string) => {
+ setMessageText("");
+ setIsLoadingSelfMessage(true);
+ let threadId = "";
+ let assistantId = "";
+
+ if (assistant?.external_chat_id?.length) {
+ const message = await createMessage(
+ messageText,
+ assistant.external_chat_id
+ );
+ setMessages((prev) => [message, ...prev]);
+ setIsLoadingSelfMessage(false);
+ setIsLoadingAdvisorMessage(true);
+ threadId = assistant.external_chat_id;
+ assistantId = assistant.external_id || "";
+ } else {
+ const thread = await createThread(messageText);
+ threadId = thread.id;
+ assistantId = assistant?.external_id || "";
+
+ await getLastMessage(threadId);
+ setIsLoadingSelfMessage(false);
+ setIsLoadingAdvisorMessage(true);
+ }
+ setTimeout(() => {
+ scrollToBottom();
+ });
+
+ const run = await runThread(threadId, assistantId);
+
+ await checkStatusAndGetLastMessage(threadId, run.id);
+ setTimeout(() => {
+ scrollToBottom();
+ });
+ setIsLoadingAdvisorMessage(false);
+ };
+
+ const handleChangeMessageText = (e: ChangeEvent) => {
+ const { scrollHeight, clientHeight, value } = e.target;
+ if (
+ scrollHeight > clientHeight &&
+ textareaRows < 5 &&
+ value.length > messageText.length
+ ) {
+ setTextareaRows((prev) => prev + 1);
+ }
+ if (
+ scrollHeight === clientHeight &&
+ textareaRows > 1 &&
+ value.length < messageText.length
+ ) {
+ setTextareaRows((prev) => prev - 1);
+ }
+ setMessageText(e.target.value);
+ };
+
+ const getIsSelfMessage = (role: string) => role === "user";
+
+ return (
+
+ {isLoading && (
+
+ )}
+ {!isLoading && (
+ navigate(-1)}
+ />
+ )}
+ {!!messages.length && (
+
+ {isLoadingAdvisorMessage && (
+
}
+ isSelf={false}
+ backgroundTextColor={"#c9c9c9"}
+ textColor={"#000"}
+ />
+ )}
+ {messages.map((message) =>
+ message.content.map((content) => (
+
+ ))
+ )}
+
+ {isLoadingLatestMessages && }
+
+
+ )}
+
+
+ {!isLoading && (
+
+ )}
+
+ );
+}
+
+export default AdvisorChatPage;
diff --git a/src/components/pages/AdvisorChat/styles.module.css b/src/components/pages/AdvisorChat/styles.module.css
new file mode 100644
index 0000000..b496526
--- /dev/null
+++ b/src/components/pages/AdvisorChat/styles.module.css
@@ -0,0 +1,52 @@
+.page {
+ position: relative;
+ height: fit-content;
+ min-height: 100dvh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-end;
+ background-color: #232322;
+ color: #fff;
+ padding: 64px 6px;
+}
+
+.header-container {
+ position: fixed;
+ top: 0;
+ left: 50%;
+ transform: translate(-50%, 0);
+ max-width: 560px;
+ z-index: 10;
+}
+
+.input-container {
+ position: fixed;
+ width: 100%;
+ bottom: 0;
+ left: 50%;
+ transform: translate(-50%, 0);
+ max-width: 560px;
+ z-index: 10;
+}
+
+.messages-container {
+ display: flex;
+ flex-direction: column-reverse;
+ gap: 16px;
+ width: 100%;
+}
+
+.loader-container {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.loader {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
diff --git a/src/components/pages/Advisors/components/AssistantCard/index.tsx b/src/components/pages/Advisors/components/AssistantCard/index.tsx
new file mode 100644
index 0000000..3c4f1a1
--- /dev/null
+++ b/src/components/pages/Advisors/components/AssistantCard/index.tsx
@@ -0,0 +1,47 @@
+import { IAssistant } from "@/api/resources/Assistants";
+import styles from "./styles.module.css";
+import MainButton from "@/components/MainButton";
+import Title from "@/components/Title";
+
+interface IAssistantCardProps extends IAssistant {
+ onClickCard: () => void;
+}
+
+function AssistantCard({
+ name,
+ expirience,
+ stars,
+ photo: { th2x },
+ rating,
+ onClickCard,
+}: IAssistantCardProps) {
+ return (
+
+
+
+ {name}
+
+
+
+
+
{expirience}
+
+
+ {Array(stars)
+ .fill(0)
+ .map((_, index) => (
+

+ ))}
+
| {rating}
+
+
+
CHAT | FREE
+
+
+ );
+}
+
+export default AssistantCard;
diff --git a/src/components/pages/Advisors/components/AssistantCard/styles.module.css b/src/components/pages/Advisors/components/AssistantCard/styles.module.css
new file mode 100644
index 0000000..427f8d4
--- /dev/null
+++ b/src/components/pages/Advisors/components/AssistantCard/styles.module.css
@@ -0,0 +1,79 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ height: fit-content;
+ border: solid #b3b3b3 1px;
+ border-radius: 12px;
+ background-color: #000;
+ justify-content: space-between;
+}
+
+.header {
+ height: 167px;
+ background-position: center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ box-shadow: inset 0 -24px 22px #000;
+ border-top-left-radius: 12px;
+ border-top-right-radius: 12px;
+ display: flex;
+ flex-direction: column-reverse;
+ padding-left: 8px;
+}
+
+.name {
+ color: #fff;
+ width: 100%;
+ text-align: left;
+ font-weight: 700;
+ font-size: 18px;
+ margin: 0;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.online-status {
+ display: block;
+ width: 9px;
+ height: 9px;
+ background-color: rgb(6, 183, 6);
+ border-radius: 50%;
+ margin-top: 4px;
+}
+
+.rating-container {
+ margin-top: 6px;
+ font-size: 12px;
+}
+
+.stars > img {
+ width: 12px;
+ margin-right: 4px;
+}
+
+.footer {
+ color: gray;
+ font-size: 14px;
+ font-weight: 500;
+ padding: 8px;
+}
+
+.button {
+ width: 100%;
+ height: 34px;
+ min-width: 0;
+ min-height: 0;
+ border-radius: 32px;
+ background-color: rgb(37, 107, 239);
+ font-size: 16px;
+ margin-top: 8px;
+}
+
+.expirience {
+ white-space: nowrap;
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ width: 100%;
+}
diff --git a/src/components/pages/Advisors/index.tsx b/src/components/pages/Advisors/index.tsx
new file mode 100644
index 0000000..aa4549b
--- /dev/null
+++ b/src/components/pages/Advisors/index.tsx
@@ -0,0 +1,60 @@
+import Title from "@/components/Title";
+import styles from "./styles.module.css";
+import { useCallback, useState } from "react";
+import { Assistants, useApi, useApiCall } from "@/api";
+import { useDispatch, useSelector } from "react-redux";
+import { selectors } from "@/store";
+import { IAssistant } from "@/api/resources/Assistants";
+import AssistantCard from "./components/AssistantCard";
+import Loader, { LoaderColor } from "@/components/Loader";
+import { useNavigate } from "react-router-dom";
+import routes from "@/routes";
+
+function Advisors() {
+ const api = useApi();
+ const dispatch = useDispatch();
+ const token = useSelector(selectors.selectToken);
+ const navigate = useNavigate();
+ const [assistants, setAssistants] = useState([]);
+ const [isLoading, setIsLoading] = useState(true);
+
+ const loadData = useCallback(async () => {
+ const { ai_assistants } = await api.assistants({
+ token,
+ });
+ setAssistants(ai_assistants);
+ setIsLoading(false);
+ return { ai_assistants };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [api, dispatch, token]);
+
+ useApiCall(loadData);
+
+ const handleAdvisorClick = (assistant: IAssistant) => {
+ navigate(routes.client.advisorChat(assistant.id));
+ };
+
+ return (
+
+
+ Advisors
+
+ {!!assistants?.length && !isLoading && (
+
+ {assistants.map((assistant, index) => (
+
handleAdvisorClick(assistant)}
+ />
+ ))}
+
+ )}
+ {isLoading && (
+
+ )}
+
+ );
+}
+
+export default Advisors;
diff --git a/src/components/pages/Advisors/styles.module.css b/src/components/pages/Advisors/styles.module.css
new file mode 100644
index 0000000..1549758
--- /dev/null
+++ b/src/components/pages/Advisors/styles.module.css
@@ -0,0 +1,28 @@
+.page {
+ position: relative;
+ height: fit-content;
+ min-height: 100dvh;
+ background-color: #232322;
+ padding-top: 32px;
+ padding-bottom: 116px;
+}
+
+.title {
+ width: 100%;
+ text-align: left;
+ color: #fff;
+}
+
+.loader {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.advisors-container {
+ width: 100%;
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(145px, 1fr));
+ gap: 12px;
+}
diff --git a/src/components/pages/QuestionnaireIntermediate/index.tsx b/src/components/pages/QuestionnaireIntermediate/index.tsx
index 17686ad..ae114af 100644
--- a/src/components/pages/QuestionnaireIntermediate/index.tsx
+++ b/src/components/pages/QuestionnaireIntermediate/index.tsx
@@ -39,7 +39,13 @@ function QuestionnaireIntermediatePage() {
backgroundImage: `url(${backgroundImage})`,
}}
>
-
+
{path && (
diff --git a/src/routes.ts b/src/routes.ts
index 1e6f378..e942fe9 100755
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -7,6 +7,8 @@ export const apiHost = "https://api-web.aura.wit.life";
const dApiHost = isProduction ? "https://d.api.witapps.us" : "https://dev.api.witapps.us"
const siteHost = "https://aura.wit.life";
const prefix = "api/v1";
+const openAIHost = " https://api.openai.com";
+const openAiPrefix = "v1";
const routes = {
client: {
@@ -116,6 +118,9 @@ const routes = {
unlimitedReadings: () => [host, "unlimited-readings"].join("/"),
addConsultation: () => [host, "add-consultation"].join("/"),
+ // Advisors
+ advisors: () => [host, "advisors"].join("/"),
+ advisorChat: (id: number) => [host, "advisors", id].join("/"),
// Email - Pay - Email
epeBirthdate: () => [host, "epe", "birthdate"].join("/"),
epePayment: () => [host, "epe", "payment"].join("/"),
@@ -178,9 +183,27 @@ const routes = {
),
getAiRequestsV2: (id: string) =>
[apiHost, "api/v2", "ai", "requests", `${id}.json`].join("/"),
+
dApiTestPaymentProducts: () =>
[dApiHost, "payment", "test", "products"].join("/"),
dApiPaymentCheckout: () => [dApiHost, "payment", "checkout"].join("/"),
+
+ assistants: () => [apiHost, prefix, "ai", "assistants.json"].join("/"),
+ setExternalChatIdAssistants: (chatId: string) =>
+ [apiHost, prefix, "ai", "assistants", chatId, "chats.json"].join("/"),
+ },
+ openAi: {
+ createThread: () => [openAIHost, openAiPrefix, "threads"].join("/"),
+ createMessage: (threadId: string) =>
+ [openAIHost, openAiPrefix, "threads", threadId, "messages"].join("/"),
+ getListMessages: (threadId: string) =>
+ [openAIHost, openAiPrefix, "threads", threadId, "messages"].join("/"),
+ runThread: (threadId: string) =>
+ [openAIHost, openAiPrefix, "threads", threadId, "runs"].join("/"),
+ getStatusRunThread: (threadId: string, runId: string) =>
+ [openAIHost, openAiPrefix, "threads", threadId, "runs", runId].join("/"),
+ getListRuns: (threadId: string) =>
+ [openAIHost, openAiPrefix, "threads", threadId, "runs"].join("/"),
},
};
@@ -199,6 +222,7 @@ export const entrypoints = [
routes.client.magicBall(),
routes.client.trialChoice(),
routes.client.palmistry(),
+ routes.client.advisors(),
];
export const isEntrypoint = (path: string) => entrypoints.includes(path);
export const isNotEntrypoint = (path: string) => !isEntrypoint(path);
@@ -284,10 +308,14 @@ export const withoutFooterRoutes = [
routes.client.addReport(),
routes.client.unlimitedReadings(),
routes.client.addConsultation(),
+ routes.client.advisors(),
routes.client.epeSuccessPayment(),
];
-export const withoutFooterPartOfRoutes = [routes.client.questionnaire()];
+export const withoutFooterPartOfRoutes = [
+ routes.client.questionnaire(),
+ routes.client.advisors(),
+];
export const hasNoFooter = (path: string) => {
const targetRoute = withoutFooterPartOfRoutes.findIndex((route) =>
@@ -304,6 +332,7 @@ export const withNavbarFooterRoutes = [
routes.client.breath(),
routes.client.breathResult(),
routes.client.wallpaper(),
+ routes.client.advisors(),
];
export const hasNavbarFooter = (path: string) =>
withNavbarFooterRoutes.includes(path);
@@ -353,6 +382,7 @@ export const withoutHeaderRoutes = [
routes.client.email("marketing-landing"),
routes.client.email("marketing-trial-payment"),
routes.client.tryApp(),
+ routes.client.advisors(),
routes.client.epeSuccessPayment(),
];
export const hasNoHeader = (path: string) => {
diff --git a/src/store/index.ts b/src/store/index.ts
index 7eeca3f..d19a036 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -16,6 +16,7 @@ import form, {
import aura, { actions as auraActions } from "./aura";
import siteConfig, {
selectHome,
+ selectOpenAiToken,
actions as siteConfigActions,
} from "./siteConfig";
import onboardingConfig, {
@@ -105,6 +106,7 @@ export const selectors = {
selectQuestionnaire,
selectUserDeviceType,
selectIsShowTryApp,
+ selectOpenAiToken,
...formSelectors,
};
diff --git a/src/store/siteConfig.ts b/src/store/siteConfig.ts
index 485aacf..8438500 100644
--- a/src/store/siteConfig.ts
+++ b/src/store/siteConfig.ts
@@ -12,6 +12,7 @@ interface ISiteConfig {
isShowNavbar: boolean;
pathFromHome: EPathsFromHome;
};
+ openAiToken: string;
}
const initialState: ISiteConfig = {
@@ -19,6 +20,7 @@ const initialState: ISiteConfig = {
isShowNavbar: false,
pathFromHome: EPathsFromHome.compatibility,
},
+ openAiToken: "",
};
const siteConfigSlice = createSlice({
@@ -37,4 +39,8 @@ export const selectHome = createSelector(
(state: { siteConfig: ISiteConfig }) => state.siteConfig.home,
(siteConfig) => siteConfig
);
+export const selectOpenAiToken = createSelector(
+ (state: { siteConfig: ISiteConfig }) => state.siteConfig.openAiToken,
+ (siteConfig) => siteConfig
+);
export default siteConfigSlice.reducer;