Skip to content

Commit 54881fa

Browse files
committed
2) Refactor DifyChatbot to use Redux state directly instead of prop callbacks
3) Move chat initialization logic to Redux initialState with localStorage check
1 parent 87a1eea commit 54881fa

File tree

6 files changed

+239
-52
lines changed

6 files changed

+239
-52
lines changed

src/common/WelcomeModal.jsx

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import {
2+
Dialog,
3+
DialogContent,
4+
Typography,
5+
IconButton,
6+
Box,
7+
Card,
8+
CardContent,
9+
Grid,
10+
} from "@mui/material";
11+
import { Close as CloseIcon, Description, SmartToy } from "@mui/icons-material";
12+
import { styled } from "@mui/material/styles";
13+
14+
const StyledDialog = styled(Dialog)(({ theme }) => ({
15+
"& .MuiDialog-paper": {
16+
borderRadius: "1rem",
17+
maxWidth: "62.5rem",
18+
width: "90%",
19+
margin: "auto",
20+
padding: 0,
21+
},
22+
}));
23+
24+
const HeaderContainer = styled(Box)({
25+
display: "flex",
26+
justifyContent: "space-between",
27+
alignItems: "flex-start",
28+
padding: "2rem 2rem 1.5rem",
29+
position: "relative",
30+
});
31+
32+
const CloseButton = styled(IconButton)({
33+
position: "absolute",
34+
top: "1rem",
35+
right: "1rem",
36+
color: "#666",
37+
"&:hover": {
38+
backgroundColor: "rgba(0, 0, 0, 0.04)",
39+
},
40+
});
41+
42+
const StyledCard = styled(Card)({
43+
height: "17.5rem",
44+
width: "17.5rem",
45+
display: "flex",
46+
flexDirection: "column",
47+
borderRadius: "0.75rem",
48+
border: "1px solid #e0e0e0",
49+
transition: "box-shadow 0.3s ease-in-out, transform 0.2s ease-in-out",
50+
cursor: "pointer",
51+
"&:hover": {
52+
boxShadow: "0 0.25rem 1.25rem rgba(0, 0, 0, 0.1)",
53+
transform: "translateY(-0.125rem)",
54+
},
55+
});
56+
57+
const CardContentWrapper = styled(CardContent)({
58+
display: "flex",
59+
flexDirection: "column",
60+
alignItems: "center",
61+
justifyContent: "center",
62+
textAlign: "center",
63+
padding: "2rem 1.5rem",
64+
height: "100%",
65+
});
66+
67+
const IconContainer = styled(Box)(({ theme }) => ({
68+
marginBottom: "1.5rem",
69+
"& svg": {
70+
fontSize: "4rem",
71+
color: theme.palette.primary.main,
72+
},
73+
}));
74+
75+
const WelcomeModal = ({ open, onClose, onOptionSelect }) => {
76+
const handleOptionClick = (option) => {
77+
onClose();
78+
// Delay action dispatch to allow modal to close first
79+
if (onOptionSelect) {
80+
setTimeout(() => {
81+
onOptionSelect(option);
82+
}, 100);
83+
}
84+
};
85+
86+
return (
87+
<StyledDialog open={open} onClose={onClose} maxWidth={false}>
88+
<HeaderContainer>
89+
<Box>
90+
<Typography variant="h4" sx={{ fontWeight: 500, marginBottom: 1 }}>
91+
Welcome to Avni!
92+
</Typography>
93+
<Typography variant="subtitle1" sx={{ fontSize: "1rem" }}>
94+
Your one stop solution to all your program needs
95+
</Typography>
96+
</Box>
97+
<CloseButton onClick={onClose}>
98+
<CloseIcon />
99+
</CloseButton>
100+
</HeaderContainer>
101+
102+
<DialogContent sx={{ padding: "0 2rem 2rem" }}>
103+
<Grid container spacing={3} justifyContent="center">
104+
<Grid item xs={12} md={4}>
105+
<StyledCard onClick={() => handleOptionClick("templates")}>
106+
<CardContentWrapper>
107+
<IconContainer>
108+
<Description />
109+
</IconContainer>
110+
<Typography
111+
variant="h6"
112+
sx={{ fontWeight: 500, marginBottom: 2 }}
113+
>
114+
Explore Avni Templates
115+
</Typography>
116+
<Typography variant="body2" sx={{ lineHeight: 1.5 }}>
117+
Learn about Avni through our varied use cases
118+
</Typography>
119+
</CardContentWrapper>
120+
</StyledCard>
121+
</Grid>
122+
123+
<Grid item xs={12} md={4}>
124+
<StyledCard onClick={() => handleOptionClick("ai")}>
125+
<CardContentWrapper>
126+
<IconContainer>
127+
<SmartToy />
128+
</IconContainer>
129+
<Typography
130+
variant="h6"
131+
sx={{ fontWeight: 500, marginBottom: 2 }}
132+
>
133+
Explore Using AI
134+
</Typography>
135+
<Typography variant="body2" sx={{ lineHeight: 1.5 }}>
136+
Design your programs from scratch using the App Designer
137+
</Typography>
138+
</CardContentWrapper>
139+
</StyledCard>
140+
</Grid>
141+
</Grid>
142+
</DialogContent>
143+
</StyledDialog>
144+
);
145+
};
146+
147+
export default WelcomeModal;

src/common/components/DifyChatbot.jsx

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,21 @@
11
import { useEffect, useState } from "react";
2-
import { useSelector } from "react-redux";
2+
import { useSelector, useDispatch } from "react-redux";
33
import { Box, Slide, useTheme, IconButton } from "@mui/material";
44
import { ChevronRight } from "@mui/icons-material";
55
import IdpDetails from "../../rootApp/security/IdpDetails.ts";
6-
const DifyChatbot = ({ onChatToggle }) => {
7-
// Check if this is first time after login
8-
const [isPanelOpen, setIsPanelOpen] = useState(() => {
9-
const hasSeenChatbot = localStorage.getItem("avni-chatbot-seen");
10-
return !hasSeenChatbot; // Open only if user hasn't seen it before
11-
});
6+
import { setChatOpen } from "../../rootApp/ducks";
7+
const DifyChatbot = () => {
8+
const dispatch = useDispatch();
129
const theme = useTheme();
1310

1411
const [aiConfig, setAiConfig] = useState(null);
1512
const [configError, setConfigError] = useState(false);
1613

17-
// Notify parent when panel state changes
18-
useEffect(() => {
19-
onChatToggle?.(isPanelOpen);
20-
}, [isPanelOpen, onChatToggle]);
14+
// Get user and organization data from Redux state
15+
const userInfo = useSelector((state) => state.app?.userInfo);
16+
const organisation = useSelector((state) => state.app?.organisation);
17+
const authSession = useSelector((state) => state.app?.authSession);
18+
const isChatOpen = useSelector((state) => state.app?.isChatOpen);
2119

2220
// Fetch AI assistant configuration
2321
useEffect(() => {
@@ -39,11 +37,6 @@ const DifyChatbot = ({ onChatToggle }) => {
3937

4038
fetchAiConfig();
4139
}, []);
42-
43-
// Get user and organization data from Redux state
44-
const userInfo = useSelector((state) => state.app?.userInfo);
45-
const organisation = useSelector((state) => state.app?.organisation);
46-
const authSession = useSelector((state) => state.app?.authSession);
4740
const authToken = localStorage.getItem(IdpDetails.AuthTokenName);
4841
const avni_mcp_server_url = aiConfig?.mcp_server_url || "";
4942

@@ -148,7 +141,7 @@ const DifyChatbot = ({ onChatToggle }) => {
148141
z-index: 1201;
149142
box-shadow: ${theme.shadows[4]};
150143
transition: all 0.3s ease;
151-
display: ${isPanelOpen ? "none" : "flex"};
144+
display: ${isChatOpen ? "none" : "flex"};
152145
align-items: center;
153146
justify-content: center;
154147
`;
@@ -164,7 +157,7 @@ const DifyChatbot = ({ onChatToggle }) => {
164157
};
165158

166159
chatButton.onclick = () => {
167-
setIsPanelOpen(true);
160+
dispatch(setChatOpen(true));
168161
};
169162

170163
document.body.appendChild(chatButton);
@@ -179,7 +172,7 @@ const DifyChatbot = ({ onChatToggle }) => {
179172
const style = document.getElementById("dify-chat-animations");
180173
if (style) style.remove();
181174
};
182-
}, [isPanelOpen, theme, aiConfig, configError]);
175+
}, [isChatOpen, theme, aiConfig, configError, dispatch]);
183176

184177
// Build chatbot URL with user context
185178
const buildChatUrl = () => {
@@ -214,7 +207,7 @@ const DifyChatbot = ({ onChatToggle }) => {
214207
return (
215208
<>
216209
{/* Embedded Right Panel */}
217-
<Slide direction="left" in={isPanelOpen} mountOnEnter unmountOnExit>
210+
<Slide direction="left" in={isChatOpen} mountOnEnter unmountOnExit>
218211
<Box
219212
sx={{
220213
position: "fixed",
@@ -233,7 +226,7 @@ const DifyChatbot = ({ onChatToggle }) => {
233226
{/* Close Button */}
234227
<IconButton
235228
onClick={() => {
236-
setIsPanelOpen(false);
229+
dispatch(setChatOpen(false));
237230
// Mark chatbot as seen so it won't auto-open again
238231
localStorage.setItem("avni-chatbot-seen", "true");
239232
}}

src/rootApp/App.jsx

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,16 @@
1-
import { useEffect, useState } from "react";
1+
import { useEffect } from "react";
22
import { useSelector, useDispatch } from "react-redux";
33
import { Box } from "@mui/material";
44
import Routes from "./Routes";
5-
import { getUserInfo, setChatOpen } from "./ducks";
5+
import { getUserInfo } from "./ducks";
66
import IdpDetails from "./security/IdpDetails";
77
import { httpClient } from "../common/utils/httpClient";
88
import Footer from "../common/components/Footer";
99
import DifyChatbot from "../common/components/DifyChatbot";
1010

1111
const App = () => {
1212
const dispatch = useDispatch();
13-
const [isChatOpen, setIsChatOpen] = useState(false);
14-
15-
// Dispatch chat state to Redux when it changes
16-
useEffect(() => {
17-
dispatch(setChatOpen(isChatOpen));
18-
}, [isChatOpen, dispatch]);
13+
const isChatOpen = useSelector((state) => state.app.isChatOpen);
1914

2015
const appInitialised = useSelector(
2116
(state) => state.app?.appInitialised || false,
@@ -69,7 +64,7 @@ const App = () => {
6964
</Box>
7065
</Box>
7166
<Footer />
72-
<DifyChatbot onChatToggle={setIsChatOpen} />
67+
<DifyChatbot />
7368
</Box>
7469
);
7570
};

src/rootApp/ducks.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const types = {
2020
LOGOUT: "app/LOGOUT",
2121
INIT_GENERIC_CONFIG: "app/INIT_GENERIC_CONFIG",
2222
SET_CHAT_OPEN: "app/SET_CHAT_OPEN",
23+
SET_IS_NEW_IMPLEMENTATION: "app/SET_IS_NEW_IMPLEMENTATION",
2324
};
2425

2526
export const getAdminOrgs = () => ({
@@ -81,6 +82,11 @@ export const setChatOpen = (isChatOpen) => ({
8182
payload: isChatOpen,
8283
});
8384

85+
export const setIsNewImplementation = (isNewImplementation) => ({
86+
type: types.SET_IS_NEW_IMPLEMENTATION,
87+
payload: isNewImplementation,
88+
});
89+
8490
const initialState = {
8591
idpDetails: undefined,
8692
authSession: new NoAuthSession(),
@@ -96,7 +102,11 @@ const initialState = {
96102
copilotEnabled: false,
97103
avniMcpServerUrl: "http://localhost:8023",
98104
},
99-
isChatOpen: false,
105+
isChatOpen: (() => {
106+
const hasSeenChatbot = localStorage.getItem("avni-chatbot-seen");
107+
return !hasSeenChatbot; // Open if user hasn't seen it before
108+
})(),
109+
isNewImplementation: false,
100110
};
101111

102112
// reducer
@@ -184,6 +194,12 @@ export default function (state = initialState, action) {
184194
isChatOpen: action.payload,
185195
};
186196
}
197+
case types.SET_IS_NEW_IMPLEMENTATION: {
198+
return {
199+
...state,
200+
isNewImplementation: action.payload,
201+
};
202+
}
187203
default:
188204
if (_.get(action, "payload.error")) console.log(action.payload.error);
189205
return state;

src/rootApp/saga.jsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
setAdminOrgs,
66
setOrganisationConfig,
77
setUserInfo,
8-
types
8+
setIsNewImplementation,
9+
types,
910
} from "./ducks";
1011
import { httpClient as http } from "common/utils/httpClient";
1112
import i18n from "i18next";
@@ -17,15 +18,15 @@ import { authProvider } from "adminApp/react-admin-config/authProvider";
1718

1819
const api = {
1920
fetchUserInfo: () =>
20-
http.fetchJson("/web/userInfo").then(response => response.json),
21+
http.fetchJson("/web/userInfo").then((response) => response.json),
2122
fetchAdminOrgs: () =>
22-
http.fetchJson("/organisation", {}, true).then(response => response.json),
23+
http.fetchJson("/organisation", {}, true).then((response) => response.json),
2324
fetchTranslations: () =>
24-
http.fetchJson("/web/translations").then(response => response.json),
25+
http.fetchJson("/web/translations").then((response) => response.json),
2526
fetchOrganisationConfig: () =>
26-
http.fetchJson("/web/organisationConfig").then(response => response.json),
27-
saveUserInfo: userInfo => http.post("/me", userInfo),
28-
logout: () => http.get("/web/logout")
27+
http.fetchJson("/web/organisationConfig").then((response) => response.json),
28+
saveUserInfo: (userInfo) => http.post("/me", userInfo),
29+
logout: () => http.get("/web/logout"),
2930
};
3031

3132
export function* onSetAuthSession() {
@@ -59,13 +60,20 @@ function* setUserDetails() {
5960

6061
const organisationName = get(userDetails, "organisationName", "");
6162
document.cookie = `IMPLEMENTATION-NAME=${encodeURIComponent(
62-
organisationName
63+
organisationName,
6364
)}; path=/; SameSite=Lax; Secure=true`;
6465

6566
if (!isEmpty(organisationName)) {
66-
const organisationConfig = yield call(api.fetchOrganisationConfig);
67+
const organisationConfigResponse = yield call(api.fetchOrganisationConfig);
6768
yield put(
68-
setOrganisationConfig(get(organisationConfig, "organisationConfig", {}))
69+
setOrganisationConfig(
70+
get(organisationConfigResponse, "organisationConfig", {}),
71+
),
72+
);
73+
yield put(
74+
setIsNewImplementation(
75+
organisationConfigResponse.isNewImplementation || false,
76+
),
6977
);
7078
}
7179

@@ -81,13 +89,13 @@ function* setUserDetails() {
8189
nsSeparator: false,
8290
interpolation: {
8391
escapeValue: false,
84-
formatSeparator: ","
92+
formatSeparator: ",",
8593
},
8694
react: {
87-
useSuspense: false
88-
}
95+
useSuspense: false,
96+
},
8997
};
90-
const init = params => i18nInstance.init(params);
98+
const init = (params) => i18nInstance.init(params);
9199
yield call(init, i18nParams);
92100

93101
yield put(sendInitComplete());

0 commit comments

Comments
 (0)