mirror of
https://github.com/hexastack/hexabot
synced 2025-06-26 18:27:28 +00:00
Merge pull request #32 from Hexastack/fix/frontend-config-runtime
Fix: load config on runtime
This commit is contained in:
commit
e97beb8a47
@ -16,6 +16,11 @@ services:
|
|||||||
- ../api/migrations:/app/migrations
|
- ../api/migrations:/app/migrations
|
||||||
#- ../api/node_modules:/app/node_modules
|
#- ../api/node_modules:/app/node_modules
|
||||||
command: ["npm", "run", "start:debug"]
|
command: ["npm", "run", "start:debug"]
|
||||||
|
|
||||||
|
hexabot-frontend:
|
||||||
|
build:
|
||||||
|
context: ../
|
||||||
|
dockerfile: ./frontend/Dockerfile
|
||||||
|
|
||||||
mongo-express:
|
mongo-express:
|
||||||
container_name: mongoUi
|
container_name: mongoUi
|
||||||
|
|||||||
@ -30,7 +30,6 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
database-init:
|
database-init:
|
||||||
condition: service_completed_successfully
|
condition: service_completed_successfully
|
||||||
|
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: "wget --spider http://localhost:3000"
|
test: "wget --spider http://localhost:3000"
|
||||||
interval: 10s
|
interval: 10s
|
||||||
@ -40,12 +39,7 @@ services:
|
|||||||
|
|
||||||
hexabot-frontend:
|
hexabot-frontend:
|
||||||
container_name: frontend
|
container_name: frontend
|
||||||
build:
|
image: hexabot-ui:latest
|
||||||
context: ../
|
|
||||||
dockerfile: ./frontend/Dockerfile
|
|
||||||
args:
|
|
||||||
- NEXT_PUBLIC_API_ORIGIN=${NEXT_PUBLIC_API_ORIGIN}
|
|
||||||
- NEXT_PUBLIC_SSO_ENABLED=${NEXT_PUBLIC_SSO_ENABLED}
|
|
||||||
env_file: .env
|
env_file: .env
|
||||||
ports:
|
ports:
|
||||||
- ${APP_FRONTEND_PORT}:8080
|
- ${APP_FRONTEND_PORT}:8080
|
||||||
|
|||||||
@ -21,14 +21,6 @@ RUN \
|
|||||||
|
|
||||||
# Rebuild the source code only when needed
|
# Rebuild the source code only when needed
|
||||||
FROM base AS builder
|
FROM base AS builder
|
||||||
ARG NEXT_PUBLIC_API_ORIGIN
|
|
||||||
ENV NEXT_PUBLIC_API_ORIGIN=${NEXT_PUBLIC_API_ORIGIN}
|
|
||||||
ARG NEXT_PUBLIC_SSO_ENABLED
|
|
||||||
ENV NEXT_PUBLIC_SSO_ENABLED=${NEXT_PUBLIC_SSO_ENABLED}
|
|
||||||
|
|
||||||
ENV REACT_APP_WIDGET_API_URL=${NEXT_PUBLIC_API_ORIGIN}
|
|
||||||
ENV REACT_APP_WIDGET_CHANNEL=test
|
|
||||||
ENV REACT_APP_WIDGET_TOKEN=test
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@ -57,10 +49,6 @@ ENV NODE_ENV production
|
|||||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
# Set the environment variable API_ORIGIN
|
|
||||||
ENV NEXT_PUBLIC_API_ORIGIN ${NEXT_PUBLIC_API_ORIGIN:-"http://localhost:3000"}
|
|
||||||
ENV NEXT_PUBLIC_SSO_ENABLED ${NEXT_PUBLIC_SSO_ENABLED:-"false"}
|
|
||||||
|
|
||||||
RUN addgroup --system --gid 1001 nodejs
|
RUN addgroup --system --gid 1001 nodejs
|
||||||
RUN adduser --system --uid 1001 nextjs
|
RUN adduser --system --uid 1001 nextjs
|
||||||
|
|
||||||
|
|||||||
@ -1,36 +1,24 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
import withTM from "next-transpile-modules";
|
import withTM from "next-transpile-modules";
|
||||||
|
|
||||||
const apiUrl = process.env.NEXT_PUBLIC_API_ORIGIN || "http://localhost:4000/";
|
|
||||||
const url = new URL(apiUrl);
|
|
||||||
const nextConfig = withTM(["hexabot-widget"])({
|
const nextConfig = withTM(["hexabot-widget"])({
|
||||||
|
async rewrites() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: "/config",
|
||||||
|
destination: "/api/config",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
webpack(config, _options) {
|
webpack(config, _options) {
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
publicRuntimeConfig: {
|
publicRuntimeConfig: {
|
||||||
apiUrl,
|
|
||||||
ssoEnabled: process.env.NEXT_PUBLIC_SSO_ENABLED === "true",
|
|
||||||
lang: {
|
lang: {
|
||||||
default: "en",
|
default: "en",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
output: "standalone",
|
output: "standalone",
|
||||||
images: {
|
|
||||||
remotePatterns: [
|
|
||||||
{
|
|
||||||
protocol: "https",
|
|
||||||
hostname: url.hostname,
|
|
||||||
port: url.port,
|
|
||||||
pathname: "/attachment/**",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
protocol: "http",
|
|
||||||
hostname: url.hostname,
|
|
||||||
port: url.port,
|
|
||||||
pathname: "/attachment/**",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@ -11,12 +11,15 @@ import { Grid } from "@mui/material";
|
|||||||
import { GridRenderCellParams } from "@mui/x-data-grid";
|
import { GridRenderCellParams } from "@mui/x-data-grid";
|
||||||
|
|
||||||
import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages";
|
import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
import { EntityType } from "@/services/types";
|
import { EntityType } from "@/services/types";
|
||||||
|
|
||||||
export const buildRenderPicture = (
|
export const buildRenderPicture = (
|
||||||
entityType: EntityType.USER | EntityType.SUBSCRIBER,
|
entityType: EntityType.USER | EntityType.SUBSCRIBER,
|
||||||
) =>
|
) =>
|
||||||
function RenderPicture(params: GridRenderCellParams) {
|
function RenderPicture(params: GridRenderCellParams) {
|
||||||
|
const { apiUrl } = useConfig();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
@ -28,6 +31,7 @@ export const buildRenderPicture = (
|
|||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={getAvatarSrc(
|
src={getAvatarSrc(
|
||||||
|
apiUrl,
|
||||||
entityType,
|
entityType,
|
||||||
entityType === EntityType.USER
|
entityType === EntityType.USER
|
||||||
? params.row.id
|
? params.row.id
|
||||||
|
|||||||
@ -21,9 +21,9 @@ import { useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import { useCreate } from "@/hooks/crud/useCreate";
|
import { useCreate } from "@/hooks/crud/useCreate";
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
import { EntityType } from "@/services/types";
|
import { EntityType } from "@/services/types";
|
||||||
|
|
||||||
|
|
||||||
import { ChatActions } from "./ChatActions";
|
import { ChatActions } from "./ChatActions";
|
||||||
import { ChatHeader } from "./ChatHeader";
|
import { ChatHeader } from "./ChatHeader";
|
||||||
import {
|
import {
|
||||||
@ -35,6 +35,7 @@ import { useChat } from "../hooks/ChatContext";
|
|||||||
import { useInfinitedLiveMessages } from "../hooks/useInfiniteLiveMessages";
|
import { useInfinitedLiveMessages } from "../hooks/useInfiniteLiveMessages";
|
||||||
|
|
||||||
export function Chat() {
|
export function Chat() {
|
||||||
|
const { apiUrl } = useConfig();
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const { subscriber } = useChat();
|
const { subscriber } = useChat();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
@ -69,7 +70,11 @@ export function Chat() {
|
|||||||
<ConversationHeader>
|
<ConversationHeader>
|
||||||
<Avatar
|
<Avatar
|
||||||
name={subscriber?.first_name}
|
name={subscriber?.first_name}
|
||||||
src={getAvatarSrc(EntityType.SUBSCRIBER, subscriber.foreign_id)}
|
src={getAvatarSrc(
|
||||||
|
apiUrl,
|
||||||
|
EntityType.SUBSCRIBER,
|
||||||
|
subscriber.foreign_id,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<ConversationHeader.Content>
|
<ConversationHeader.Content>
|
||||||
<ChatHeader />
|
<ChatHeader />
|
||||||
@ -118,6 +123,7 @@ export function Chat() {
|
|||||||
i18n.language,
|
i18n.language,
|
||||||
)}`}
|
)}`}
|
||||||
src={getAvatarSrc(
|
src={getAvatarSrc(
|
||||||
|
apiUrl,
|
||||||
message.sender
|
message.sender
|
||||||
? EntityType.SUBSCRIBER
|
? EntityType.SUBSCRIBER
|
||||||
: EntityType.USER,
|
: EntityType.USER,
|
||||||
|
|||||||
@ -18,12 +18,14 @@ import { Input } from "@/app-components/inputs/Input";
|
|||||||
import { useFind } from "@/hooks/crud/useFind";
|
import { useFind } from "@/hooks/crud/useFind";
|
||||||
import { useUpdate } from "@/hooks/crud/useUpdate";
|
import { useUpdate } from "@/hooks/crud/useUpdate";
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
import { EntityType } from "@/services/types";
|
import { EntityType } from "@/services/types";
|
||||||
|
|
||||||
import { getAvatarSrc } from "../helpers/mapMessages";
|
import { getAvatarSrc } from "../helpers/mapMessages";
|
||||||
import { useChat } from "../hooks/ChatContext";
|
import { useChat } from "../hooks/ChatContext";
|
||||||
|
|
||||||
export const ChatActions = () => {
|
export const ChatActions = () => {
|
||||||
|
const { apiUrl } = useConfig();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { subscriber: activeChat } = useChat();
|
const { subscriber: activeChat } = useChat();
|
||||||
const [takeoverBy, setTakeoverBy] = useState<string>(
|
const [takeoverBy, setTakeoverBy] = useState<string>(
|
||||||
@ -57,7 +59,7 @@ export const ChatActions = () => {
|
|||||||
<Avatar
|
<Avatar
|
||||||
size="sm"
|
size="sm"
|
||||||
name={user.first_name}
|
name={user.first_name}
|
||||||
src={getAvatarSrc(EntityType.USER, user.id)}
|
src={getAvatarSrc(apiUrl, EntityType.USER, user.id)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import InboxIcon from "@mui/icons-material/MoveToInbox";
|
|||||||
import { Chip, debounce, Grid } from "@mui/material";
|
import { Chip, debounce, Grid } from "@mui/material";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
import { Title } from "@/layout/content/Title";
|
import { Title } from "@/layout/content/Title";
|
||||||
import { EntityType } from "@/services/types";
|
import { EntityType } from "@/services/types";
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ export const SubscribersList = (props: {
|
|||||||
searchPayload: any;
|
searchPayload: any;
|
||||||
assignedTo: AssignedTo;
|
assignedTo: AssignedTo;
|
||||||
}) => {
|
}) => {
|
||||||
|
const { apiUrl } = useConfig();
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const chat = useChat();
|
const chat = useChat();
|
||||||
const { fetchNextPage, isFetching, subscribers, hasNextPage } =
|
const { fetchNextPage, isFetching, subscribers, hasNextPage } =
|
||||||
@ -58,6 +60,7 @@ export const SubscribersList = (props: {
|
|||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={getAvatarSrc(
|
src={getAvatarSrc(
|
||||||
|
apiUrl,
|
||||||
EntityType.SUBSCRIBER,
|
EntityType.SUBSCRIBER,
|
||||||
conversation.foreign_id,
|
conversation.foreign_id,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -11,18 +11,16 @@ import { Message, MessageModel } from "@chatscope/chat-ui-kit-react";
|
|||||||
import MenuRoundedIcon from "@mui/icons-material/MenuRounded";
|
import MenuRoundedIcon from "@mui/icons-material/MenuRounded";
|
||||||
import ReplyIcon from "@mui/icons-material/Reply";
|
import ReplyIcon from "@mui/icons-material/Reply";
|
||||||
import { Chip, Grid } from "@mui/material";
|
import { Chip, Grid } from "@mui/material";
|
||||||
import getConfig from "next/config";
|
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
import { ROUTES } from "@/services/api.class";
|
import { ROUTES } from "@/services/api.class";
|
||||||
import { EntityType } from "@/services/types";
|
import { EntityType } from "@/services/types";
|
||||||
import { IMessage, IMessageFull } from "@/types/message.types";
|
import { IMessage, IMessageFull } from "@/types/message.types";
|
||||||
|
import { buildURL } from "@/utils/URL";
|
||||||
|
|
||||||
import { AttachmentViewer } from "../components/AttachmentViewer";
|
import { AttachmentViewer } from "../components/AttachmentViewer";
|
||||||
import { Carousel } from "../components/Carousel";
|
import { Carousel } from "../components/Carousel";
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
|
||||||
|
|
||||||
function hasSameSender(
|
function hasSameSender(
|
||||||
m1: IMessage | IMessageFull,
|
m1: IMessage | IMessageFull,
|
||||||
m2: IMessage | IMessageFull,
|
m2: IMessage | IMessageFull,
|
||||||
@ -133,13 +131,11 @@ export function getMessageContent(
|
|||||||
* @description Returns the avatar of the subscriber
|
* @description Returns the avatar of the subscriber
|
||||||
*/
|
*/
|
||||||
export function getAvatarSrc(
|
export function getAvatarSrc(
|
||||||
|
apiUrl: string,
|
||||||
entity: EntityType.USER | EntityType.SUBSCRIBER,
|
entity: EntityType.USER | EntityType.SUBSCRIBER,
|
||||||
id?: string,
|
id?: string,
|
||||||
) {
|
) {
|
||||||
//remove trailing slash
|
return buildURL(apiUrl, `${ROUTES[entity]}/${id || "bot"}/profile_pic`);
|
||||||
return `${String(publicRuntimeConfig.apiUrl).replace(/\/$/, "")}${
|
|
||||||
ROUTES[entity]
|
|
||||||
}/${id || "bot"}/profile_pic`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessagePosition(
|
export function getMessagePosition(
|
||||||
@ -186,4 +182,4 @@ export function getMessagePosition(
|
|||||||
|
|
||||||
// Default case (should not reach here)
|
// Default case (should not reach here)
|
||||||
return "single";
|
return "single";
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { GridColDef } from "@mui/x-data-grid";
|
import { GridColDef } from "@mui/x-data-grid";
|
||||||
import getConfig from "next/config";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -38,6 +37,7 @@ import { DataGrid } from "@/app-components/tables/DataGrid";
|
|||||||
import { useDelete } from "@/hooks/crud/useDelete";
|
import { useDelete } from "@/hooks/crud/useDelete";
|
||||||
import { useFind } from "@/hooks/crud/useFind";
|
import { useFind } from "@/hooks/crud/useFind";
|
||||||
import { useGetFromCache } from "@/hooks/crud/useGet";
|
import { useGetFromCache } from "@/hooks/crud/useGet";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
|
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
|
||||||
import { useHasPermission } from "@/hooks/useHasPermission";
|
import { useHasPermission } from "@/hooks/useHasPermission";
|
||||||
import { useSearch } from "@/hooks/useSearch";
|
import { useSearch } from "@/hooks/useSearch";
|
||||||
@ -56,7 +56,6 @@ import { buildURL } from "@/utils/URL";
|
|||||||
import { NlpImportDialog } from "../NlpImportDialog";
|
import { NlpImportDialog } from "../NlpImportDialog";
|
||||||
import { NlpSampleDialog } from "../NlpSampleDialog";
|
import { NlpSampleDialog } from "../NlpSampleDialog";
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
|
||||||
const NLP_SAMPLE_TYPE_COLORS = {
|
const NLP_SAMPLE_TYPE_COLORS = {
|
||||||
test: "#e6a23c",
|
test: "#e6a23c",
|
||||||
train: "#67c23a",
|
train: "#67c23a",
|
||||||
@ -64,6 +63,7 @@ const NLP_SAMPLE_TYPE_COLORS = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function NlpSample() {
|
export default function NlpSample() {
|
||||||
|
const { apiUrl } = useConfig();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [dataset, setDataSet] = useState("");
|
const [dataset, setDataSet] = useState("");
|
||||||
@ -287,7 +287,7 @@ export default function NlpSample() {
|
|||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
href={buildURL(
|
href={buildURL(
|
||||||
publicRuntimeConfig.apiUrl,
|
apiUrl,
|
||||||
`nlpsample/export${dataset ? `?type=${dataset}` : ""}`,
|
`nlpsample/export${dataset ? `?type=${dataset}` : ""}`,
|
||||||
)}
|
)}
|
||||||
startIcon={<DownloadIcon />}
|
startIcon={<DownloadIcon />}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import { faUsers } from "@fortawesome/free-solid-svg-icons";
|
|||||||
import PersonAddAlt1Icon from "@mui/icons-material/PersonAddAlt1";
|
import PersonAddAlt1Icon from "@mui/icons-material/PersonAddAlt1";
|
||||||
import { Button, Grid, Paper } from "@mui/material";
|
import { Button, Grid, Paper } from "@mui/material";
|
||||||
import { GridColDef } from "@mui/x-data-grid";
|
import { GridColDef } from "@mui/x-data-grid";
|
||||||
import getConfig from "next/config";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { ChipEntity } from "@/app-components/displays/ChipEntity";
|
import { ChipEntity } from "@/app-components/displays/ChipEntity";
|
||||||
@ -25,6 +24,7 @@ import { buildRenderPicture } from "@/app-components/tables/columns/renderPictur
|
|||||||
import { DataGrid } from "@/app-components/tables/DataGrid";
|
import { DataGrid } from "@/app-components/tables/DataGrid";
|
||||||
import { useFind } from "@/hooks/crud/useFind";
|
import { useFind } from "@/hooks/crud/useFind";
|
||||||
import { useUpdate } from "@/hooks/crud/useUpdate";
|
import { useUpdate } from "@/hooks/crud/useUpdate";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
|
import { getDisplayDialogs, useDialog } from "@/hooks/useDialog";
|
||||||
import { useHasPermission } from "@/hooks/useHasPermission";
|
import { useHasPermission } from "@/hooks/useHasPermission";
|
||||||
import { useSearch } from "@/hooks/useSearch";
|
import { useSearch } from "@/hooks/useSearch";
|
||||||
@ -39,9 +39,8 @@ import { getDateTimeFormatter } from "@/utils/date";
|
|||||||
import { EditUserDialog } from "./EditUserDialog";
|
import { EditUserDialog } from "./EditUserDialog";
|
||||||
import { InvitationDialog } from "./InvitationDialog";
|
import { InvitationDialog } from "./InvitationDialog";
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
|
||||||
|
|
||||||
export const Users = () => {
|
export const Users = () => {
|
||||||
|
const { ssoEnabled } = useConfig();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { mutateAsync: updateUser } = useUpdate(EntityType.USER, {
|
const { mutateAsync: updateUser } = useUpdate(EntityType.USER, {
|
||||||
@ -157,7 +156,7 @@ export const Users = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={publicRuntimeConfig.ssoEnabled}
|
disabled={ssoEnabled}
|
||||||
>
|
>
|
||||||
{t(params.row.state ? "label.enabled" : "label.disabled")}
|
{t(params.row.state ? "label.enabled" : "label.disabled")}
|
||||||
</Button>
|
</Button>
|
||||||
@ -188,7 +187,7 @@ export const Users = () => {
|
|||||||
valueGetter: (params) =>
|
valueGetter: (params) =>
|
||||||
t("datetime.updated_at", getDateTimeFormatter(params)),
|
t("datetime.updated_at", getDateTimeFormatter(params)),
|
||||||
},
|
},
|
||||||
...(!publicRuntimeConfig.ssoEnabled ? [actionColumns] : []),
|
...(!ssoEnabled ? [actionColumns] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -207,7 +206,7 @@ export const Users = () => {
|
|||||||
<Grid item>
|
<Grid item>
|
||||||
<FilterTextfield onChange={onSearch} />
|
<FilterTextfield onChange={onSearch} />
|
||||||
</Grid>
|
</Grid>
|
||||||
{!publicRuntimeConfig.ssoEnabled &&
|
{!ssoEnabled &&
|
||||||
hasPermission(EntityType.USER, PermissionAction.CREATE) ? (
|
hasPermission(EntityType.USER, PermissionAction.CREATE) ? (
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@ -8,7 +8,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import getConfig from "next/config";
|
|
||||||
import { stringify } from "qs";
|
import { stringify } from "qs";
|
||||||
import React, { createContext, ReactNode, useContext, useMemo } from "react";
|
import React, { createContext, ReactNode, useContext, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -18,17 +17,17 @@ import { EntityType } from "@/services/types";
|
|||||||
import { IBaseSchema } from "@/types/base.types";
|
import { IBaseSchema } from "@/types/base.types";
|
||||||
|
|
||||||
import { useLogoutRedirection } from "./useAuth";
|
import { useLogoutRedirection } from "./useAuth";
|
||||||
|
import { useConfig } from "./useConfig";
|
||||||
import { useToast } from "./useToast";
|
import { useToast } from "./useToast";
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
|
||||||
|
|
||||||
export const useAxiosInstance = () => {
|
export const useAxiosInstance = () => {
|
||||||
|
const { apiUrl } = useConfig();
|
||||||
const { logoutRedirection } = useLogoutRedirection();
|
const { logoutRedirection } = useLogoutRedirection();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const axiosInstance = useMemo(() => {
|
const axiosInstance = useMemo(() => {
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
baseURL: publicRuntimeConfig.apiUrl,
|
baseURL: apiUrl,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
});
|
});
|
||||||
// Added the same Query String (de)Serializer as NestJS,
|
// Added the same Query String (de)Serializer as NestJS,
|
||||||
|
|||||||
47
frontend/src/hooks/useConfig.tsx
Normal file
47
frontend/src/hooks/useConfig.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { createContext, useContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
const ConfigContext = createContext<IConfig | null>(null);
|
||||||
|
|
||||||
|
export interface IConfig {
|
||||||
|
apiUrl: string;
|
||||||
|
ssoEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ConfigProvider = ({ children }) => {
|
||||||
|
const [config, setConfig] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchConfig = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/config");
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
setConfig(data);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error("Failed to fetch configuration:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!config) {
|
||||||
|
// You can return a loader here if you want
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigContext.Provider value={config}>{children}</ConfigContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useConfig = () => {
|
||||||
|
const context = useContext(ConfigContext);
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useConfig must be used within a ConfigProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@ -18,7 +18,6 @@ import {
|
|||||||
styled,
|
styled,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
|
import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
|
||||||
import getConfig from "next/config";
|
|
||||||
import { FC, useEffect, useRef, useState } from "react";
|
import { FC, useEffect, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
@ -26,12 +25,11 @@ import { HexabotLogo } from "@/app-components/logos/HexabotLogo";
|
|||||||
import { PopoverMenu } from "@/app-components/menus/PopoverMenu";
|
import { PopoverMenu } from "@/app-components/menus/PopoverMenu";
|
||||||
import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages";
|
import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages";
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
import { EntityType } from "@/services/types";
|
import { EntityType } from "@/services/types";
|
||||||
|
|
||||||
import { borderLine, theme } from "./themes/theme";
|
import { borderLine, theme } from "./themes/theme";
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
|
||||||
|
|
||||||
interface AppBarProps extends MuiAppBarProps {
|
interface AppBarProps extends MuiAppBarProps {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
}
|
}
|
||||||
@ -75,6 +73,7 @@ export type HeaderProps = {
|
|||||||
onToggleSidebar: () => void;
|
onToggleSidebar: () => void;
|
||||||
};
|
};
|
||||||
export const Header: FC<HeaderProps> = ({ isSideBarOpen, onToggleSidebar }) => {
|
export const Header: FC<HeaderProps> = ({ isSideBarOpen, onToggleSidebar }) => {
|
||||||
|
const { apiUrl, ssoEnabled } = useConfig();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const anchorRef = useRef(null);
|
const anchorRef = useRef(null);
|
||||||
const [isMenuPopoverOpen, setIsMenuPopoverOpen] = useState(false);
|
const [isMenuPopoverOpen, setIsMenuPopoverOpen] = useState(false);
|
||||||
@ -160,7 +159,7 @@ export const Header: FC<HeaderProps> = ({ isSideBarOpen, onToggleSidebar }) => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={getAvatarSrc(EntityType.USER, user?.id).concat(
|
src={getAvatarSrc(apiUrl, EntityType.USER, user?.id).concat(
|
||||||
`?${randomSeed}`,
|
`?${randomSeed}`,
|
||||||
)}
|
)}
|
||||||
color={theme.palette.text.secondary}
|
color={theme.palette.text.secondary}
|
||||||
@ -176,7 +175,7 @@ export const Header: FC<HeaderProps> = ({ isSideBarOpen, onToggleSidebar }) => {
|
|||||||
last_name: user.last_name,
|
last_name: user.last_name,
|
||||||
}}
|
}}
|
||||||
links={
|
links={
|
||||||
!publicRuntimeConfig.ssoEnabled
|
!ssoEnabled
|
||||||
? [
|
? [
|
||||||
{ text: t("menu.home"), href: "/" },
|
{ text: t("menu.home"), href: "/" },
|
||||||
{ text: t("menu.edit_account"), href: "/profile" },
|
{ text: t("menu.edit_account"), href: "/profile" },
|
||||||
|
|||||||
@ -31,7 +31,6 @@ import SettingsAccessibilityRoundedIcon from "@mui/icons-material/SettingsAccess
|
|||||||
import { CSSObject, Grid, IconButton, styled, Theme } from "@mui/material";
|
import { CSSObject, Grid, IconButton, styled, Theme } from "@mui/material";
|
||||||
import MuiDrawer from "@mui/material/Drawer";
|
import MuiDrawer from "@mui/material/Drawer";
|
||||||
import { OverridableComponent } from "@mui/material/OverridableComponent";
|
import { OverridableComponent } from "@mui/material/OverridableComponent";
|
||||||
import getConfig from "next/config";
|
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { FC, useMemo } from "react";
|
import { FC, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@ -39,12 +38,12 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { HexabotLogo } from "@/app-components/logos/HexabotLogo";
|
import { HexabotLogo } from "@/app-components/logos/HexabotLogo";
|
||||||
import { Sidebar } from "@/app-components/menus/Sidebar";
|
import { Sidebar } from "@/app-components/menus/Sidebar";
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
import { useHasPermission } from "@/hooks/useHasPermission";
|
import { useHasPermission } from "@/hooks/useHasPermission";
|
||||||
import { EntityType } from "@/services/types";
|
import { EntityType } from "@/services/types";
|
||||||
import { PermissionAction } from "@/types/permission.types";
|
import { PermissionAction } from "@/types/permission.types";
|
||||||
import { getLayout } from "@/utils/laylout";
|
import { getLayout } from "@/utils/laylout";
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
|
||||||
const drawerWidth = 280;
|
const drawerWidth = 280;
|
||||||
const openedMixin = (theme: Theme): CSSObject => ({
|
const openedMixin = (theme: Theme): CSSObject => ({
|
||||||
width: drawerWidth,
|
width: drawerWidth,
|
||||||
@ -107,7 +106,7 @@ export type MenuItem = {
|
|||||||
submenuItems?: MenuItem[];
|
submenuItems?: MenuItem[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const MENU_ITEMS: MenuItem[] = [
|
const getMenuItems = (ssoEnabled: boolean): MenuItem[] => [
|
||||||
{
|
{
|
||||||
text: "menu.dashboard",
|
text: "menu.dashboard",
|
||||||
href: "/",
|
href: "/",
|
||||||
@ -236,7 +235,7 @@ const MENU_ITEMS: MenuItem[] = [
|
|||||||
[EntityType.USER]: [PermissionAction.READ],
|
[EntityType.USER]: [PermissionAction.READ],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...(!publicRuntimeConfig.ssoEnabled
|
...(!ssoEnabled
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
text: "menu.roles",
|
text: "menu.roles",
|
||||||
@ -271,13 +270,15 @@ export const VerticalMenu: FC<VerticalMenuProps> = ({
|
|||||||
onToggleIn,
|
onToggleIn,
|
||||||
onToggleOut,
|
onToggleOut,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { ssoEnabled } = useConfig();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const hasPermission = useHasPermission();
|
const hasPermission = useHasPermission();
|
||||||
|
const menuItems = getMenuItems(ssoEnabled);
|
||||||
// Filter menu item to which user is allowed access
|
// Filter menu item to which user is allowed access
|
||||||
const availableMenuItems = useMemo(() => {
|
const availableMenuItems = useMemo(() => {
|
||||||
return MENU_ITEMS.filter(({ requires: requiredPermissions }) => {
|
return menuItems.filter(({ requires: requiredPermissions }) => {
|
||||||
return (
|
return (
|
||||||
!requiredPermissions ||
|
!requiredPermissions ||
|
||||||
Object.entries(requiredPermissions).every((permission) => {
|
Object.entries(requiredPermissions).every((permission) => {
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { ReactQueryDevtools } from "react-query/devtools";
|
|||||||
import { SnackbarCloseButton } from "@/app-components/displays/Toast/CloseButton";
|
import { SnackbarCloseButton } from "@/app-components/displays/Toast/CloseButton";
|
||||||
import { ApiClientProvider } from "@/hooks/useApiClient";
|
import { ApiClientProvider } from "@/hooks/useApiClient";
|
||||||
import { AuthProvider } from "@/hooks/useAuth";
|
import { AuthProvider } from "@/hooks/useAuth";
|
||||||
|
import { ConfigProvider } from "@/hooks/useConfig";
|
||||||
import { PermissionProvider } from "@/hooks/useHasPermission";
|
import { PermissionProvider } from "@/hooks/useHasPermission";
|
||||||
import { SettingsProvider } from "@/hooks/useSetting";
|
import { SettingsProvider } from "@/hooks/useSetting";
|
||||||
import { ToastProvider } from "@/hooks/useToast";
|
import { ToastProvider } from "@/hooks/useToast";
|
||||||
@ -69,33 +70,35 @@ const App = ({ Component, pageProps }: TAppPropsWithLayout) => {
|
|||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
<main className={roboto.className}>
|
<main className={roboto.className}>
|
||||||
<ThemeProvider theme={theme}>
|
<ConfigProvider>
|
||||||
<ToastProvider
|
<ThemeProvider theme={theme}>
|
||||||
maxSnack={3}
|
<ToastProvider
|
||||||
anchorOrigin={{ vertical: "top", horizontal: "center" }}
|
maxSnack={3}
|
||||||
action={(snackbarKey) => (
|
anchorOrigin={{ vertical: "top", horizontal: "center" }}
|
||||||
<SnackbarCloseButton snackbarKey={snackbarKey} />
|
action={(snackbarKey) => (
|
||||||
)}
|
<SnackbarCloseButton snackbarKey={snackbarKey} />
|
||||||
>
|
)}
|
||||||
<StyledEngineProvider injectFirst>
|
>
|
||||||
<QueryClientProvider client={queryClient}>
|
<StyledEngineProvider injectFirst>
|
||||||
<CssBaseline />
|
<QueryClientProvider client={queryClient}>
|
||||||
<ApiClientProvider>
|
<CssBaseline />
|
||||||
<AuthProvider>
|
<ApiClientProvider>
|
||||||
<PermissionProvider>
|
<AuthProvider>
|
||||||
<SettingsProvider>
|
<PermissionProvider>
|
||||||
<SocketProvider>
|
<SettingsProvider>
|
||||||
{getLayout(<Component {...pageProps} />)}
|
<SocketProvider>
|
||||||
</SocketProvider>
|
{getLayout(<Component {...pageProps} />)}
|
||||||
</SettingsProvider>
|
</SocketProvider>
|
||||||
</PermissionProvider>
|
</SettingsProvider>
|
||||||
</AuthProvider>
|
</PermissionProvider>
|
||||||
</ApiClientProvider>
|
</AuthProvider>
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
</ApiClientProvider>
|
||||||
</QueryClientProvider>
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
</StyledEngineProvider>
|
</QueryClientProvider>
|
||||||
</ToastProvider>
|
</StyledEngineProvider>
|
||||||
</ThemeProvider>
|
</ToastProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</ConfigProvider>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
16
frontend/src/pages/api/config.ts
Normal file
16
frontend/src/pages/api/config.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
type ResponseData = {
|
||||||
|
apiUrl: string;
|
||||||
|
ssoEnabled: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse<ResponseData>,
|
||||||
|
) {
|
||||||
|
res.status(200).json({
|
||||||
|
apiUrl: process.env.NEXT_PUBLIC_API_ORIGIN || "http://localhost:4000",
|
||||||
|
ssoEnabled: process.env.NEXT_PUBLIC_SSO_ENABLED === "true" || false,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -10,17 +10,16 @@
|
|||||||
import ChatIcon from "@mui/icons-material/Chat";
|
import ChatIcon from "@mui/icons-material/Chat";
|
||||||
import { Avatar, Box, Typography } from "@mui/material";
|
import { Avatar, Box, Typography } from "@mui/material";
|
||||||
import UiChatWidget from "hexabot-widget/src/UiChatWidget";
|
import UiChatWidget from "hexabot-widget/src/UiChatWidget";
|
||||||
import getConfig from "next/config";
|
|
||||||
import { ReactElement } from "react";
|
import { ReactElement } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages";
|
import { getAvatarSrc } from "@/components/inbox/helpers/mapMessages";
|
||||||
import { VisualEditor } from "@/components/visual-editor";
|
import { VisualEditor } from "@/components/visual-editor";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
import i18n from "@/i18n/config";
|
import i18n from "@/i18n/config";
|
||||||
import { Layout } from "@/layout";
|
import { Layout } from "@/layout";
|
||||||
import { EntityType } from "@/services/types";
|
import { EntityType } from "@/services/types";
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
|
||||||
const CustomWidgetHeader = () => {
|
const CustomWidgetHeader = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -34,12 +33,14 @@ const CustomWidgetHeader = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
const VisualEditorPage = () => {
|
const VisualEditorPage = () => {
|
||||||
|
const { apiUrl } = useConfig();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<VisualEditor />
|
<VisualEditor />
|
||||||
<UiChatWidget
|
<UiChatWidget
|
||||||
config={{
|
config={{
|
||||||
apiUrl: publicRuntimeConfig.apiUrl,
|
apiUrl,
|
||||||
channel: "live-chat-tester",
|
channel: "live-chat-tester",
|
||||||
token: "test",
|
token: "test",
|
||||||
language: i18n.language,
|
language: i18n.language,
|
||||||
@ -48,7 +49,7 @@ const VisualEditorPage = () => {
|
|||||||
CustomAvatar={() => (
|
CustomAvatar={() => (
|
||||||
<Avatar
|
<Avatar
|
||||||
sx={{ width: "32px", height: "32px", fontSize: ".75rem" }}
|
sx={{ width: "32px", height: "32px", fontSize: ".75rem" }}
|
||||||
src={getAvatarSrc(EntityType.USER, "bot")}
|
src={getAvatarSrc(apiUrl, EntityType.USER, "bot")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -7,13 +7,10 @@
|
|||||||
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
* 3. SaaS Restriction: This software, or any derivative of it, may not be used to offer a competing product or service (SaaS) without prior written consent from Hexastack. Offering the software as a service or using it in a commercial cloud environment without express permission is strictly prohibited.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import getConfig from "next/config";
|
|
||||||
import { io, Socket, ManagerOptions, SocketOptions } from "socket.io-client";
|
import { io, Socket, ManagerOptions, SocketOptions } from "socket.io-client";
|
||||||
|
|
||||||
import { IOIncomingMessage, IOOutgoingMessage } from "./types/io-message";
|
import { IOIncomingMessage, IOOutgoingMessage } from "./types/io-message";
|
||||||
|
|
||||||
const { publicRuntimeConfig } = getConfig();
|
|
||||||
|
|
||||||
type SocketIoClientConfig = Partial<ManagerOptions & SocketOptions>;
|
type SocketIoClientConfig = Partial<ManagerOptions & SocketOptions>;
|
||||||
|
|
||||||
export class SocketIoClient {
|
export class SocketIoClient {
|
||||||
@ -52,13 +49,13 @@ export class SocketIoClient {
|
|||||||
|
|
||||||
private initialized: boolean = false;
|
private initialized: boolean = false;
|
||||||
|
|
||||||
constructor(socketConfig?: SocketIoClientConfig) {
|
constructor(apiUrl: string, socketConfig?: SocketIoClientConfig) {
|
||||||
this.config = {
|
this.config = {
|
||||||
...SocketIoClient.defaultConfig,
|
...SocketIoClient.defaultConfig,
|
||||||
...socketConfig,
|
...socketConfig,
|
||||||
autoConnect: false,
|
autoConnect: false,
|
||||||
};
|
};
|
||||||
const url = new URL(publicRuntimeConfig.apiUrl);
|
const url = new URL(apiUrl);
|
||||||
|
|
||||||
this.socket = io(url.origin, this.config);
|
this.socket = io(url.origin, this.config);
|
||||||
}
|
}
|
||||||
@ -160,5 +157,3 @@ export class SocketIoClient {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const socketIoClient = new SocketIoClient();
|
|
||||||
|
|||||||
@ -12,22 +12,24 @@ import {
|
|||||||
PropsWithChildren,
|
PropsWithChildren,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { QueryOptions, useQuery } from "react-query";
|
import { QueryOptions, useQuery } from "react-query";
|
||||||
|
|
||||||
import { useAuth } from "@/hooks/useAuth";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
import { useToast } from "@/hooks/useToast";
|
import { useToast } from "@/hooks/useToast";
|
||||||
|
|
||||||
import { SocketIoClient, socketIoClient } from "./SocketIoClient";
|
import { SocketIoClient } from "./SocketIoClient";
|
||||||
|
|
||||||
interface socketContext {
|
interface socketContext {
|
||||||
socket: SocketIoClient;
|
socket: SocketIoClient | null;
|
||||||
connected: boolean;
|
connected: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const socketContext = createContext<socketContext>({
|
const socketContext = createContext<socketContext>({
|
||||||
socket: socketIoClient,
|
socket: null,
|
||||||
connected: false,
|
connected: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -36,13 +38,14 @@ export const useSocket = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const SocketProvider = (props: PropsWithChildren) => {
|
export const SocketProvider = (props: PropsWithChildren) => {
|
||||||
const { socket } = useSocket();
|
const { apiUrl } = useConfig();
|
||||||
const [connected, setConnected] = useState(false);
|
const [connected, setConnected] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
const socket = useMemo(() => new SocketIoClient(apiUrl), [apiUrl]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user)
|
if (user && apiUrl)
|
||||||
socket.init({
|
socket.init({
|
||||||
onConnect: () => {
|
onConnect: () => {
|
||||||
setConnected(true);
|
setConnected(true);
|
||||||
@ -54,7 +57,7 @@ export const SocketProvider = (props: PropsWithChildren) => {
|
|||||||
setConnected(false);
|
setConnected(false);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [socket, toast, user]);
|
}, [socket, toast, user, apiUrl]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<socketContext.Provider value={{ socket, connected }}>
|
<socketContext.Provider value={{ socket, connected }}>
|
||||||
@ -73,9 +76,9 @@ export const useSubscribe = <T,>(event: string, callback: (arg: T) => void) => {
|
|||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket.on<T>(event, callback);
|
socket?.on<T>(event, callback);
|
||||||
|
|
||||||
return () => socket.off(event, callback);
|
return () => socket?.off(event, callback);
|
||||||
}, [event, callback, socket]);
|
}, [event, callback, socket]);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -83,14 +86,17 @@ export const useSocketGetQuery = <T,>(
|
|||||||
url: string,
|
url: string,
|
||||||
options?: Omit<QueryOptions<T, Error, T, string[]>, "queryFn">,
|
options?: Omit<QueryOptions<T, Error, T, string[]>, "queryFn">,
|
||||||
) => {
|
) => {
|
||||||
|
const { socket } = useSocket();
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
...options,
|
...options,
|
||||||
queryKey: ["socket", "get", url],
|
queryKey: ["socket", "get", url],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await socketIoClient.get<T>(url);
|
if (!socket) throw new Error("Socket not initialized");
|
||||||
|
const response = await socket.get<T>(url);
|
||||||
|
|
||||||
return response.body;
|
return response.body;
|
||||||
},
|
},
|
||||||
|
enabled: !!socket,
|
||||||
});
|
});
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user