UI Redesin!
This commit is contained in:
@@ -19,55 +19,58 @@ import DocumentStoragePage from './llm-fe/pages/DocumentStoragePage/DocumentStor
|
||||
import Account2 from './llm-fe/pages/Account2/Account2';
|
||||
import AnalyticsPage from './llm-fe/pages/Analytics/Analytics';
|
||||
import PasswordResetConfirmation from './llm-fe/pages/PasswordResetConfirmation/PasswordReset';
|
||||
import GlobalThemeWrapper from './llm-fe/components/GlobalThemeWrapper/GlobalThemeWrapper';
|
||||
|
||||
const ProtectedRoutes = () => {
|
||||
const { authenticated, needsNewPassword, loading } = useContext(AuthContext);
|
||||
if(loading){
|
||||
return(
|
||||
if (loading) {
|
||||
return (
|
||||
<div>Loading</div>
|
||||
)
|
||||
}
|
||||
if(!authenticated) return <Navigate to='/signin/' replace />
|
||||
if (!authenticated) return <Navigate to='/signin/' replace />
|
||||
//if(!needsNewPassword) return <Navigate to='/password_reset/' replace/>
|
||||
|
||||
return <Outlet key={Date.now()}/>
|
||||
return <Outlet key={Date.now()} />
|
||||
}
|
||||
|
||||
class App extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className='site'>
|
||||
<main>
|
||||
<div className="main-container">
|
||||
<GlobalThemeWrapper>
|
||||
<div className='site'>
|
||||
<main>
|
||||
<div className="main-container">
|
||||
|
||||
|
||||
<Routes>
|
||||
<Route path='*' element={<NotFound />} />
|
||||
<Routes>
|
||||
<Route path='*' element={<NotFound />} />
|
||||
|
||||
<Route path={"/signin/"} Component={SignIn}/>
|
||||
<Route path={"/password_reset/"} Component={PasswordReset}/>
|
||||
<Route path={"/password_reset_confirmation/"} Component={PasswordResetConfirmation}/>
|
||||
<Route path={'/set_password/'} Component={SetPassword}/>
|
||||
<Route path={"/signin/"} Component={SignIn} />
|
||||
<Route path={"/password_reset/"} Component={PasswordReset} />
|
||||
<Route path={"/password_reset_confirmation/"} Component={PasswordResetConfirmation} />
|
||||
<Route path={'/set_password/'} Component={SetPassword} />
|
||||
|
||||
|
||||
|
||||
<Route element={<ProtectedRoutes />}>
|
||||
<Route path={"/"} index={true} Component={AsyncDashboard2}/>
|
||||
<Route path={"/account/"} Component={Account2}/>
|
||||
<Route path={"/document_storage"} Component={DocumentStoragePage}/>
|
||||
<Route path={"/terms_of_service/"} Component={TermsOfService}/>
|
||||
<Route path={"/feedback/"} Component={FeedbackPage2}/>
|
||||
<Route path={"/analytics/"} Component={AnalyticsPage} />
|
||||
<Route element={<ProtectedRoutes />}>
|
||||
<Route path={"/"} index={true} Component={AsyncDashboard2} />
|
||||
<Route path={"/account/"} Component={Account2} />
|
||||
<Route path={"/document_storage"} Component={DocumentStoragePage} />
|
||||
<Route path={"/terms_of_service/"} Component={TermsOfService} />
|
||||
<Route path={"/feedback/"} Component={FeedbackPage2} />
|
||||
<Route path={"/analytics/"} Component={AnalyticsPage} />
|
||||
|
||||
</Route>
|
||||
</Route>
|
||||
|
||||
</Routes>
|
||||
</Routes>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</GlobalThemeWrapper>
|
||||
|
||||
|
||||
);
|
||||
|
||||
@@ -1,95 +1,97 @@
|
||||
import axios from "axios";
|
||||
const Cookies = require('js-cookie');
|
||||
const Cookies = require("js-cookie");
|
||||
|
||||
//const baseURL = 'http://127.0.0.1:8011/api/';
|
||||
const baseURL = 'https://chatbackend.aimloperations.com/api/';
|
||||
const baseURL = "http://localhost:8011/api/";
|
||||
//const baseURL = 'https://chatbackend.aimloperations.com/api/';
|
||||
//const baseURL = process.env.REACT_APP_BACKEND_REST_API_BASE_URL;
|
||||
|
||||
export const axiosInstance = axios.create({
|
||||
baseURL: baseURL,
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
"Authorization": 'JWT ' + localStorage.getItem('access_token'),
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
baseURL: baseURL,
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
Authorization: "JWT " + localStorage.getItem("access_token"),
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
export const cleanAxiosInstance = axios.create({
|
||||
baseURL: baseURL,
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
baseURL: baseURL,
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
export const axiosInstanceCSRF = axios.create({
|
||||
baseURL: baseURL,
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'X-CSRFToken': Cookies.get('csrftoken'), // Include CSRF token in headers
|
||||
},
|
||||
withCredentials: true,
|
||||
}
|
||||
);
|
||||
baseURL: baseURL,
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
"X-CSRFToken": Cookies.get("csrftoken"), // Include CSRF token in headers
|
||||
},
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
axiosInstance.interceptors.request.use(config => {
|
||||
config.timeout = 100000;
|
||||
return config;
|
||||
})
|
||||
axiosInstance.interceptors.request.use((config) => {
|
||||
config.timeout = 100000;
|
||||
return config;
|
||||
});
|
||||
|
||||
axiosInstance.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
const originalRequest = error.config;
|
||||
(response) => response,
|
||||
(error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
// Prevent infinite loop
|
||||
if (error.response.status === 401 && originalRequest.url === baseURL+'/token/refresh/') {
|
||||
window.location.href = '/signin/';
|
||||
//console.log('remove the local storage here')
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if(error.response.data.code === "token_not_valid" &&
|
||||
error.response.status == 401 &&
|
||||
error.response.statusText == 'Unauthorized')
|
||||
{
|
||||
const refresh_token = localStorage.getItem('refresh_token');
|
||||
|
||||
if (refresh_token){
|
||||
const tokenParts = JSON.parse(atob(refresh_token.split('.')[1]));
|
||||
|
||||
const now = Math.ceil(Date.now() / 1000);
|
||||
//console.log(tokenParts.exp)
|
||||
|
||||
if(tokenParts.exp > now){
|
||||
return axiosInstance.post('/token/refresh/', {refresh: refresh_token}).then((response) => {
|
||||
localStorage.setItem('access_token', response.data.access);
|
||||
localStorage.setItem('refresh_token', response.data.refresh);
|
||||
|
||||
axiosInstance.defaults.headers['Authorization'] = 'JWT ' + response.data.access;
|
||||
originalRequest.headers['Authorization'] = 'JWT ' + response.data.access;
|
||||
|
||||
return axiosInstance(originalRequest);
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
});
|
||||
|
||||
}else{
|
||||
console.log('Refresh token is expired');
|
||||
window.location.href = '/signin/';
|
||||
|
||||
}
|
||||
}else {
|
||||
console.log('Refresh token not available');
|
||||
window.location.href = '/signin/';
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
return Promise.reject(error);
|
||||
// Prevent infinite loop
|
||||
if (
|
||||
error.response.status === 401 &&
|
||||
originalRequest.url === baseURL + "/token/refresh/"
|
||||
) {
|
||||
window.location.href = "/signin/";
|
||||
//console.log('remove the local storage here')
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
if (
|
||||
error.response.data.code === "token_not_valid" &&
|
||||
error.response.status == 401 &&
|
||||
error.response.statusText == "Unauthorized"
|
||||
) {
|
||||
const refresh_token = localStorage.getItem("refresh_token");
|
||||
|
||||
if (refresh_token) {
|
||||
const tokenParts = JSON.parse(atob(refresh_token.split(".")[1]));
|
||||
|
||||
const now = Math.ceil(Date.now() / 1000);
|
||||
//console.log(tokenParts.exp)
|
||||
|
||||
if (tokenParts.exp > now) {
|
||||
return axiosInstance
|
||||
.post("/token/refresh/", { refresh: refresh_token })
|
||||
.then((response) => {
|
||||
localStorage.setItem("access_token", response.data.access);
|
||||
localStorage.setItem("refresh_token", response.data.refresh);
|
||||
|
||||
axiosInstance.defaults.headers["Authorization"] =
|
||||
"JWT " + response.data.access;
|
||||
originalRequest.headers["Authorization"] =
|
||||
"JWT " + response.data.access;
|
||||
|
||||
return axiosInstance(originalRequest);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
} else {
|
||||
console.log("Refresh token is expired");
|
||||
window.location.href = "/signin/";
|
||||
}
|
||||
} else {
|
||||
console.log("Refresh token not available");
|
||||
window.location.href = "/signin/";
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
@@ -1,22 +1,79 @@
|
||||
import React from "react";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import { Card, CardContent, CircularProgress } from "@mui/material";
|
||||
import styled from "styled-components";
|
||||
import styled, { keyframes } from "styled-components";
|
||||
|
||||
const StyleDiv = styled.div`
|
||||
background-color: #f0f0f0;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
const fadeIn = keyframes`
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
`;
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
const MessageContainer = styled.div<{ $isUser: boolean }>`
|
||||
display: flex;
|
||||
justify-content: ${(props) => (props.$isUser ? "flex-end" : "flex-start")};
|
||||
margin-bottom: 1.5rem;
|
||||
width: 100%;
|
||||
animation: ${fadeIn} 0.3s ease-out;
|
||||
`;
|
||||
|
||||
const Bubble = styled.div<{ $isUser: boolean }>`
|
||||
max-width: 80%;
|
||||
padding: 1rem 1.5rem;
|
||||
border-radius: 1.2rem;
|
||||
background: ${(props) =>
|
||||
props.$isUser
|
||||
? `linear-gradient(135deg, ${props.theme.main} 0%, ${props.theme.focus} 100%)`
|
||||
: props.theme.darkMode
|
||||
? "rgba(255, 255, 255, 0.1)"
|
||||
: "rgba(0, 0, 0, 0.7)"}; // Dark background for AI in light mode as requested
|
||||
color: #fff; // Text stays white as background is always dark/colored
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid ${(props) => props.theme.darkMode ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.1)"};
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
font-size: 1rem;
|
||||
line-height: 1.6;
|
||||
border-bottom-right-radius: ${(props) => (props.$isUser ? "0.2rem" : "1.2rem")};
|
||||
border-bottom-left-radius: ${(props) => (props.$isUser ? "1.2rem" : "0.2rem")};
|
||||
|
||||
& pre {
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
span {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
& code {
|
||||
font-family: 'Fira Code', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
& a {
|
||||
color: #a0c4ff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
const LoadingDot = styled.div`
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
background: #fff;
|
||||
border-radius: 50%;
|
||||
margin: 0 4px;
|
||||
animation: bounce 1.4s infinite ease-in-out both;
|
||||
|
||||
&:nth-child(1) { animation-delay: -0.32s; }
|
||||
&:nth-child(2) { animation-delay: -0.16s; }
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 80%, 100% { transform: scale(0); }
|
||||
40% { transform: scale(1); }
|
||||
}
|
||||
`;
|
||||
|
||||
const LoadingContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0.5rem;
|
||||
`;
|
||||
|
||||
type ConversationDetailCardProps = {
|
||||
@@ -30,7 +87,7 @@ const MyPlot = ({ format, image }: { format: string; image: string }) => {
|
||||
return (
|
||||
<img
|
||||
src={imageSrc}
|
||||
style={{ maxWidth: "100%", height: "auto" }}
|
||||
style={{ maxWidth: "100%", height: "auto", borderRadius: "8px", marginTop: "10px" }}
|
||||
alt="plot"
|
||||
/>
|
||||
);
|
||||
@@ -39,7 +96,9 @@ const MyPlot = ({ format, image }: { format: string; image: string }) => {
|
||||
// Custom component for rendering errors
|
||||
const MyError = ({ content }: { content: string }) => {
|
||||
return (
|
||||
<span style={{ color: "red", fontWeight: "bold" }}>Error: {content}</span>
|
||||
<span style={{ color: "#ff6b6b", fontWeight: "bold", display: "block", marginTop: "0.5rem" }}>
|
||||
Error: {content}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -47,85 +106,66 @@ const ConversationDetailCard = ({
|
||||
message,
|
||||
user_created,
|
||||
}: ConversationDetailCardProps): JSX.Element => {
|
||||
const text_align = user_created ? "right" : "left";
|
||||
if (message.length === 0) {
|
||||
return (
|
||||
<Card sx={{ margin: "0.25rem" }}>
|
||||
<CardContent
|
||||
className="card-body-small text-dark "
|
||||
style={{
|
||||
textAlign: `right`,
|
||||
marginRight: "1rem",
|
||||
marginLeft: "1rem",
|
||||
marginTop: "1rem",
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
<CircularProgress color="inherit" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
} else {
|
||||
let contentToAdd = message;
|
||||
console.log(contentToAdd);
|
||||
try {
|
||||
const parsedMessage = JSON.parse(message);
|
||||
if (
|
||||
parsedMessage &&
|
||||
typeof parsedMessage === "object" &&
|
||||
parsedMessage.type
|
||||
) {
|
||||
switch (parsedMessage.type) {
|
||||
case "text":
|
||||
contentToAdd = parsedMessage.content;
|
||||
|
||||
break;
|
||||
case "plot":
|
||||
contentToAdd = `<plot format="${parsedMessage.format}" image="${parsedMessage.image}"></plot>`;
|
||||
break;
|
||||
case "error":
|
||||
contentToAdd = `<error content="${parsedMessage.content}"></error>`;
|
||||
break;
|
||||
default:
|
||||
// contentToAdd is already `message`
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
console.log(contentToAdd);
|
||||
|
||||
return (
|
||||
<Card sx={{ margin: "0.25rem" }}>
|
||||
<CardContent
|
||||
sx={{
|
||||
textAlign: `${text_align}`,
|
||||
marginRight: "1rem",
|
||||
marginLeft: "1rem",
|
||||
marginTop: "1rem",
|
||||
marginBottom: "1rem",
|
||||
}}
|
||||
>
|
||||
<Markdown
|
||||
className="display-linebreak"
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
options={{
|
||||
overrides: {
|
||||
plot: {
|
||||
component: MyPlot,
|
||||
},
|
||||
error: {
|
||||
component: MyError,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{contentToAdd}
|
||||
</Markdown>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<MessageContainer $isUser={false}>
|
||||
<Bubble $isUser={false}>
|
||||
<LoadingContainer>
|
||||
<LoadingDot />
|
||||
<LoadingDot />
|
||||
<LoadingDot />
|
||||
</LoadingContainer>
|
||||
</Bubble>
|
||||
</MessageContainer>
|
||||
);
|
||||
}
|
||||
|
||||
let contentToAdd = message;
|
||||
try {
|
||||
const parsedMessage = JSON.parse(message);
|
||||
if (
|
||||
parsedMessage &&
|
||||
typeof parsedMessage === "object" &&
|
||||
parsedMessage.type
|
||||
) {
|
||||
switch (parsedMessage.type) {
|
||||
case "text":
|
||||
contentToAdd = parsedMessage.content;
|
||||
break;
|
||||
case "plot":
|
||||
contentToAdd = `<plot format="${parsedMessage.format}" image="${parsedMessage.image}"></plot>`;
|
||||
break;
|
||||
case "error":
|
||||
contentToAdd = `<error content="${parsedMessage.content}"></error>`;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch { }
|
||||
|
||||
return (
|
||||
<MessageContainer $isUser={user_created}>
|
||||
<Bubble $isUser={user_created}>
|
||||
<Markdown
|
||||
className="display-linebreak"
|
||||
style={{ whiteSpace: "pre-line" }}
|
||||
options={{
|
||||
overrides: {
|
||||
plot: {
|
||||
component: MyPlot,
|
||||
},
|
||||
error: {
|
||||
component: MyError,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
{contentToAdd}
|
||||
</Markdown>
|
||||
</Bubble>
|
||||
</MessageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConversationDetailCard;
|
||||
|
||||
@@ -8,36 +8,39 @@ import { CacheProvider } from "@emotion/react";
|
||||
import { CssBaseline, Icon, ThemeProvider } from "@mui/material";
|
||||
import brandWhite from "../../ui-kit/assets/images/logo-ct.png";
|
||||
import brandDark from "../../ui-kit/assets/images/logo-ct-dark.png";
|
||||
import themeDark from "../../ui-kit/assets/theme-dark";
|
||||
import createDarkTheme from "../../ui-kit/assets/theme-dark";
|
||||
import themeDarkRTL from "../../ui-kit/assets/theme-dark/theme-rtl";
|
||||
import theme from "../../ui-kit/assets/theme";
|
||||
import createTheme from "../../ui-kit/assets/theme";
|
||||
import themeRTL from "../../ui-kit/assets/theme/theme-rtl";
|
||||
import palettes from "../../ui-kit/assets/theme/base/palettes";
|
||||
import Sidenav from "../../ui-kit/examples/Sidenav";
|
||||
import Configurator from "../../ui-kit/examples/Configurator";
|
||||
|
||||
|
||||
type DashboardWrapperLayoutProps = {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const DashboardWrapperLayout = ({children}: DashboardWrapperLayoutProps): JSX.Element => {
|
||||
const DashboardWrapperLayout = ({ children }: DashboardWrapperLayoutProps): JSX.Element => {
|
||||
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
miniSidenav,
|
||||
direction,
|
||||
layout,
|
||||
openConfigurator,
|
||||
sidenavColor,
|
||||
transparentSidenav,
|
||||
whiteSidenav,
|
||||
darkMode,
|
||||
} = controller;
|
||||
const [onMouseEnter, setOnMouseEnter] = useState(false);
|
||||
const [rtlCache, setRtlCache] = useState<EmotionCache | null>(null);
|
||||
const { pathname } = useLocation();
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
miniSidenav,
|
||||
direction,
|
||||
layout,
|
||||
openConfigurator,
|
||||
sidenavColor,
|
||||
transparentSidenav,
|
||||
whiteSidenav,
|
||||
|
||||
// Cache for the rtl
|
||||
darkMode,
|
||||
themeColor,
|
||||
} = controller;
|
||||
const [onMouseEnter, setOnMouseEnter] = useState(false);
|
||||
const [rtlCache, setRtlCache] = useState<EmotionCache | null>(null);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
// Cache for the rtl
|
||||
useMemo(() => {
|
||||
const cacheRtl = createCache({
|
||||
key: "rtl",
|
||||
@@ -74,8 +77,8 @@ const DashboardWrapperLayout = ({children}: DashboardWrapperLayoutProps): JSX.El
|
||||
// Setting page scroll to 0 when changing the route
|
||||
useEffect(() => {
|
||||
document.documentElement.scrollTop = 0;
|
||||
if(document.scrollingElement){
|
||||
document.scrollingElement.scrollTop = 0;
|
||||
if (document.scrollingElement) {
|
||||
document.scrollingElement.scrollTop = 0;
|
||||
}
|
||||
}, [pathname]);
|
||||
|
||||
@@ -126,7 +129,7 @@ const DashboardWrapperLayout = ({children}: DashboardWrapperLayoutProps): JSX.El
|
||||
</ThemeProvider>
|
||||
</CacheProvider>
|
||||
) : (
|
||||
<ThemeProvider theme={darkMode ? themeDark : theme}>
|
||||
<ThemeProvider theme={darkMode ? createDarkTheme((palettes as any)[themeColor] || (palettes as any).blue) : createTheme((palettes as any)[themeColor] || (palettes as any).blue)}>
|
||||
<CssBaseline />
|
||||
{layout === "dashboard" && (
|
||||
<>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
import { useMaterialUIController } from '../../ui-kit/context';
|
||||
import palettes from '../../ui-kit/assets/theme/base/palettes';
|
||||
|
||||
type GlobalThemeWrapperProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const GlobalThemeWrapper = ({ children }: GlobalThemeWrapperProps): JSX.Element => {
|
||||
const [controller] = useMaterialUIController();
|
||||
const { themeColor, darkMode } = controller;
|
||||
|
||||
// Default to blue if themeColor is not found
|
||||
const selectedPalette = palettes[themeColor as keyof typeof palettes] || palettes.blue;
|
||||
|
||||
const theme = {
|
||||
main: selectedPalette.main,
|
||||
focus: selectedPalette.focus,
|
||||
darkMode: darkMode,
|
||||
// Add other global theme variables here if needed
|
||||
colors: {
|
||||
background: darkMode ? '#1a2035' : '#f0f2f5',
|
||||
text: darkMode ? '#ffffff' : '#344767',
|
||||
cardBackground: darkMode ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.8)',
|
||||
cardBorder: darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)',
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={theme}>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalThemeWrapper;
|
||||
@@ -1,104 +1,185 @@
|
||||
import { AppBar, Button, Toolbar } from '@mui/material';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useMaterialUIController } from '../../ui-kit/context';
|
||||
import { boolean } from 'yup';
|
||||
import MDBox from '../../ui-kit/components/MDBox';
|
||||
import {
|
||||
navbar,
|
||||
navbarContainer,
|
||||
navbarRow,
|
||||
navbarIconButton,
|
||||
navbarMobileMenu,
|
||||
} from "../../ui-kit/examples/Navbars/DashboardNavbar/styles";
|
||||
import Breadcrumbs from '../../ui-kit/examples/Breadcrumbs';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import { AuthContext } from '../../contexts/AuthContext';
|
||||
import styled, { useTheme } from 'styled-components';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AuthContext } from '../../contexts/AuthContext';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
|
||||
const HeaderContainer = styled.header`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 1rem 2rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
z-index: 100;
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(0, 0, 0, 0.2)' : 'rgba(255, 255, 255, 0.2)'};
|
||||
backdrop-filter: blur(5px);
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
`;
|
||||
|
||||
const Logo = styled.div`
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
cursor: pointer;
|
||||
background: ${({ theme }) => `linear-gradient(135deg, ${theme.main} 0%, ${theme.focus} 100%)`};
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
`;
|
||||
|
||||
const Nav = styled.nav`
|
||||
display: flex;
|
||||
gap: 1.5rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const NavLink = styled.button`
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
opacity: 0.7;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'};
|
||||
}
|
||||
`;
|
||||
|
||||
const SignOutButton = styled(NavLink)`
|
||||
color: #ff6b6b;
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
color: #ff4757;
|
||||
background: rgba(255, 71, 87, 0.1);
|
||||
}
|
||||
`;
|
||||
|
||||
const MobileMenuButton = styled.button`
|
||||
display: none;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
z-index: 101;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
|
||||
const MobileMenuDropdown = styled.div<{ isOpen: boolean }>`
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
background: ${({ theme }) => theme.colors.cardBackground};
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
transform: ${({ isOpen }) => (isOpen ? 'translateY(0)' : 'translateY(-20px)')};
|
||||
opacity: ${({ isOpen }) => (isOpen ? '1' : '0')};
|
||||
pointer-events: ${({ isOpen }) => (isOpen ? 'auto' : 'none')};
|
||||
transition: all 0.3s ease;
|
||||
z-index: 99;
|
||||
`;
|
||||
|
||||
const HamburgerIcon = ({ color }: { color: string }) => (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3 12H21" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M3 6H21" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M3 18H21" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const CloseIcon = ({ color }: { color: string }) => (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 6L6 18" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
<path d="M6 6L18 18" stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
type Header2Props = {
|
||||
absolute?: Boolean;
|
||||
light?: Boolean;
|
||||
isMini?: Boolean;
|
||||
absolute?: Boolean;
|
||||
light?: Boolean;
|
||||
isMini?: Boolean;
|
||||
}
|
||||
|
||||
const Header2 = ({ absolute=false, light=false, isMini=false }: Header2Props): JSX.Element => {
|
||||
const {authenticated, setAuthentication} = useContext(AuthContext);
|
||||
const { account, setAccount } = useContext(AccountContext);
|
||||
const navigate = useNavigate();
|
||||
const handleSignOut = async () => {
|
||||
try{
|
||||
const response = await axiosInstance.post('blacklist/',{
|
||||
'refresh_token': localStorage.getItem("refresh_token")
|
||||
})
|
||||
localStorage.removeItem('access_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
axiosInstance.defaults.headers['Authorization'] = null;
|
||||
setAuthentication(false)
|
||||
setAccount(undefined);
|
||||
navigate('/signin/')
|
||||
}finally{
|
||||
const Header2 = ({ absolute = false, light = false, isMini = false }: Header2Props): JSX.Element => {
|
||||
const { setAuthentication } = useContext(AuthContext);
|
||||
const { setAccount } = useContext(AccountContext);
|
||||
const navigate = useNavigate();
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const theme = useTheme();
|
||||
|
||||
}
|
||||
}
|
||||
const handleSignOut = async () => {
|
||||
try {
|
||||
await axiosInstance.post('blacklist/', {
|
||||
'refresh_token': localStorage.getItem("refresh_token")
|
||||
})
|
||||
localStorage.removeItem('access_token')
|
||||
localStorage.removeItem('refresh_token')
|
||||
axiosInstance.defaults.headers['Authorization'] = null;
|
||||
setAuthentication(false)
|
||||
setAccount(undefined);
|
||||
navigate('/signin/')
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
const handleDashboardClick = async () => {
|
||||
navigate('/')
|
||||
}
|
||||
const handleNavClick = (path: string) => {
|
||||
navigate(path);
|
||||
setIsMenuOpen(false);
|
||||
};
|
||||
|
||||
const handleDocumentStorageClick = async () => {
|
||||
navigate('/document_storage/')
|
||||
}
|
||||
return (
|
||||
<HeaderContainer>
|
||||
<Logo onClick={() => navigate('/')}>
|
||||
<h4>Chat</h4>
|
||||
|
||||
const handleAccountClick = async () => {
|
||||
navigate('/account/')
|
||||
}
|
||||
</Logo>
|
||||
|
||||
const handleFeedbackClick = async () => {
|
||||
navigate('/feedback/')
|
||||
}
|
||||
{/* Desktop Nav */}
|
||||
<Nav>
|
||||
<NavLink onClick={() => navigate('/')}>Dashboard</NavLink>
|
||||
<NavLink onClick={() => navigate('/account/')}>Account</NavLink>
|
||||
<NavLink onClick={() => navigate('/document_storage/')}>Documents</NavLink>
|
||||
<NavLink onClick={() => navigate('/analytics/')}>Analytics</NavLink>
|
||||
<NavLink onClick={() => navigate('/feedback/')}>Feedback</NavLink>
|
||||
<SignOutButton onClick={handleSignOut}>Sign Out</SignOutButton>
|
||||
</Nav>
|
||||
|
||||
const handleAnalyticsClick = async () => {
|
||||
navigate('/analytics/')
|
||||
}
|
||||
const [navbarType, setNavbarType] = useState();
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const { miniSidenav, transparentNavbar, fixedNavbar, openConfigurator, darkMode } = controller;
|
||||
return (
|
||||
<AppBar
|
||||
position={absolute ? 'absolute' : navbarType}
|
||||
color='info'
|
||||
sx={(theme) => navbar(theme, { transparentNavbar, absolute, light, darkMode })}
|
||||
>
|
||||
{/* <Toolbar> sx={(theme) => navbarContainer(theme)}> */}
|
||||
<Toolbar sx={{justifyContent: 'start'}}>
|
||||
<MDBox>
|
||||
<Button color="inherit" onClick={handleDashboardClick}>Dashboard</Button>
|
||||
{/* Mobile Menu Button */}
|
||||
<MobileMenuButton onClick={() => setIsMenuOpen(!isMenuOpen)}>
|
||||
{isMenuOpen ? <CloseIcon color={theme.colors.text} /> : <HamburgerIcon color={theme.colors.text} />}
|
||||
</MobileMenuButton>
|
||||
|
||||
</MDBox>
|
||||
<MDBox sx={{marginLeft: "auto"}}>
|
||||
<Button color="inherit" onClick={handleAccountClick}>Account</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleDocumentStorageClick}>Document Storage</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleAnalyticsClick}>Analytics</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleFeedbackClick} >Feedback</Button>
|
||||
|
||||
<Button color="inherit" onClick={handleSignOut} >Sign Out</Button>
|
||||
|
||||
</MDBox>
|
||||
|
||||
|
||||
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
|
||||
|
||||
|
||||
)
|
||||
{/* Mobile Menu Dropdown */}
|
||||
<MobileMenuDropdown isOpen={isMenuOpen}>
|
||||
<NavLink onClick={() => handleNavClick('/')}>Dashboard</NavLink>
|
||||
<NavLink onClick={() => handleNavClick('/account/')}>Account</NavLink>
|
||||
<NavLink onClick={() => handleNavClick('/document_storage/')}>Documents</NavLink>
|
||||
<NavLink onClick={() => handleNavClick('/analytics/')}>Analytics</NavLink>
|
||||
<NavLink onClick={() => handleNavClick('/feedback/')}>Feedback</NavLink>
|
||||
<SignOutButton onClick={() => { handleSignOut(); setIsMenuOpen(false); }}>Sign Out</SignOutButton>
|
||||
</MobileMenuDropdown>
|
||||
</HeaderContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header2;
|
||||
@@ -6,27 +6,29 @@ import stylisRTLPlugin from "stylis-plugin-rtl";
|
||||
import MDBox from "../../ui-kit/components/MDBox";
|
||||
import { CacheProvider } from "@emotion/react";
|
||||
import { CssBaseline, Icon, ThemeProvider } from "@mui/material";
|
||||
import themeDark from "../../ui-kit/assets/theme-dark";
|
||||
import theme from "../../ui-kit/assets/theme";
|
||||
import createTheme from "../../ui-kit/assets/theme";
|
||||
import createDarkTheme from "../../ui-kit/assets/theme-dark";
|
||||
import themeRtl from "../../ui-kit/assets/theme/theme-rtl";
|
||||
import themeDarkRTL from "../../ui-kit/assets/theme-dark/theme-rtl";
|
||||
import palettes from "../../ui-kit/assets/theme/base/palettes";
|
||||
|
||||
type PageWrapperLayoutProps = {
|
||||
children: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PageWrapperLayout = ({children}: PageWrapperLayoutProps): JSX.Element => {
|
||||
const PageWrapperLayout = ({ children }: PageWrapperLayoutProps): JSX.Element => {
|
||||
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
direction,
|
||||
openConfigurator,
|
||||
darkMode,
|
||||
} = controller;
|
||||
const [rtlCache, setRtlCache] = useState<EmotionCache | null>(null);
|
||||
const { pathname } = useLocation();
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
direction,
|
||||
openConfigurator,
|
||||
darkMode,
|
||||
themeColor,
|
||||
} = controller;
|
||||
const [rtlCache, setRtlCache] = useState<EmotionCache | null>(null);
|
||||
const { pathname } = useLocation();
|
||||
|
||||
// Cache for the rtl
|
||||
// Cache for the rtl
|
||||
useMemo(() => {
|
||||
const cacheRtl = createCache({
|
||||
key: "rtl",
|
||||
@@ -47,8 +49,8 @@ const PageWrapperLayout = ({children}: PageWrapperLayoutProps): JSX.Element => {
|
||||
// Setting page scroll to 0 when changing the route
|
||||
useEffect(() => {
|
||||
document.documentElement.scrollTop = 0;
|
||||
if(document.scrollingElement){
|
||||
document.scrollingElement.scrollTop = 0;
|
||||
if (document.scrollingElement) {
|
||||
document.scrollingElement.scrollTop = 0;
|
||||
}
|
||||
}, [pathname]);
|
||||
|
||||
@@ -86,9 +88,9 @@ const PageWrapperLayout = ({children}: PageWrapperLayoutProps): JSX.Element => {
|
||||
</ThemeProvider>
|
||||
</CacheProvider>
|
||||
) : (
|
||||
<ThemeProvider theme={darkMode ? themeDark : theme}>
|
||||
<ThemeProvider theme={darkMode ? createDarkTheme((palettes as any)[themeColor] || (palettes as any).blue) : createTheme((palettes as any)[themeColor] || (palettes as any).blue)}>
|
||||
<CssBaseline />
|
||||
{children}
|
||||
{children}
|
||||
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import styled, { useTheme } from 'styled-components';
|
||||
|
||||
const Canvas = styled.canvas`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
background: ${({ theme }) => theme.darkMode ? 'linear-gradient(to bottom, #000000, #1a1a2e)' : 'linear-gradient(to bottom, #f0f2f5, #e0e5ec)'};
|
||||
transition: background 0.5s ease;
|
||||
`;
|
||||
|
||||
const ParticleBackground = () => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const theme = useTheme();
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
let particles: Particle[] = [];
|
||||
let animationFrameId: number;
|
||||
|
||||
const resizeCanvas = () => {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
};
|
||||
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
resizeCanvas();
|
||||
|
||||
class Particle {
|
||||
x: number;
|
||||
y: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
size: number;
|
||||
color: string;
|
||||
|
||||
constructor() {
|
||||
this.x = Math.random() * canvas!.width;
|
||||
this.y = Math.random() * canvas!.height;
|
||||
this.vx = (Math.random() - 0.5) * 0.5;
|
||||
this.vy = (Math.random() - 0.5) * 0.5;
|
||||
this.size = Math.random() * 2 + 1;
|
||||
// Use theme main color or fallback
|
||||
const baseColor = theme?.main || '#6495ed';
|
||||
// Convert hex to rgb for opacity if needed, or just use it directly if it's already compatible
|
||||
// For simplicity, let's assume theme.main is a hex and we want to add opacity.
|
||||
// But here we are using a fixed color for now, let's try to use the theme color.
|
||||
this.color = theme?.darkMode
|
||||
? `rgba(100, 149, 237, ${Math.random() * 0.5 + 0.2})`
|
||||
: `rgba(100, 149, 237, ${Math.random() * 0.5 + 0.2})`; // Keep blueish for now, maybe change later
|
||||
|
||||
// Better approach: use the theme main color
|
||||
this.color = theme?.main
|
||||
? `${theme.main}${Math.floor((Math.random() * 0.5 + 0.2) * 255).toString(16).padStart(2, '0')}`
|
||||
: `rgba(100, 149, 237, ${Math.random() * 0.5 + 0.2})`;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.x += this.vx;
|
||||
this.y += this.vy;
|
||||
|
||||
if (this.x < 0 || this.x > canvas!.width) this.vx *= -1;
|
||||
if (this.y < 0 || this.y > canvas!.height) this.vy *= -1;
|
||||
}
|
||||
|
||||
draw() {
|
||||
if (!ctx) return;
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
particles = [];
|
||||
const numberOfParticles = Math.floor((canvas.width * canvas.height) / 15000);
|
||||
for (let i = 0; i < numberOfParticles; i++) {
|
||||
particles.push(new Particle());
|
||||
}
|
||||
};
|
||||
|
||||
const animate = () => {
|
||||
if (!ctx) return;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
particles.forEach((particle) => {
|
||||
particle.update();
|
||||
particle.draw();
|
||||
});
|
||||
|
||||
// Draw connections
|
||||
for (let i = 0; i < particles.length; i++) {
|
||||
for (let j = i + 1; j < particles.length; j++) {
|
||||
const dx = particles[i].x - particles[j].x;
|
||||
const dy = particles[i].y - particles[j].y;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (distance < 100) {
|
||||
ctx.beginPath();
|
||||
// Use theme main color for connections too
|
||||
const opacity = 1 - distance / 100;
|
||||
const hexOpacity = Math.floor(opacity * 255).toString(16).padStart(2, '0');
|
||||
ctx.strokeStyle = theme?.main
|
||||
? `${theme.main}${hexOpacity}`
|
||||
: `rgba(100, 149, 237, ${opacity})`;
|
||||
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.moveTo(particles[i].x, particles[i].y);
|
||||
ctx.lineTo(particles[j].x, particles[j].y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
animationFrameId = requestAnimationFrame(animate);
|
||||
};
|
||||
|
||||
init();
|
||||
animate();
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', resizeCanvas);
|
||||
cancelAnimationFrame(animationFrameId);
|
||||
};
|
||||
}, [theme]); // Re-run when theme changes
|
||||
|
||||
return <Canvas ref={canvasRef} />;
|
||||
};
|
||||
|
||||
export default ParticleBackground;
|
||||
@@ -5,41 +5,48 @@ import { Alert, Button, Stack, Typography } from '@mui/material';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
|
||||
import background from '../../../bg.jpeg'
|
||||
|
||||
import * as Yup from 'yup';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
import PageWrapperLayout from '../PageWrapperLayout/PageWrapperLayout';
|
||||
import MDBox from '../../ui-kit/components/MDBox';
|
||||
import { Card, CardContent } from '@mui/material';
|
||||
import MDTypography from '../../ui-kit/components/MDTypography';
|
||||
import MDButton from '../../ui-kit/components/MDButton';
|
||||
import ParticleBackground from '../../components/ParticleBackground/ParticleBackground';
|
||||
|
||||
|
||||
export type PasswordResetValues = {
|
||||
password1: string;
|
||||
password2: string;
|
||||
password1: string;
|
||||
password2: string;
|
||||
};
|
||||
|
||||
const initialValues = {password1: '', password2: ''}
|
||||
const validationSchema = Yup.object().shape({
|
||||
const initialValues = { password1: '', password2: '' }
|
||||
const validationSchema = Yup.object().shape({
|
||||
password1: Yup.string().min(6, "Passwords have to be at least 6 digits").required(),
|
||||
password2: Yup.string().min(6, "Passwords have to be at least 6 digits").required().oneOf([Yup.ref('password1')], "Passwords must match"),
|
||||
})
|
||||
|
||||
const contains_number = (item: string): boolean => {
|
||||
const numbers = ['1','2','3','4','5','6','7','8','9','0'];
|
||||
const numbers = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
|
||||
const hasNumber = numbers.some((character) => item.includes(character));
|
||||
if(hasNumber){
|
||||
if (hasNumber) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const contains_special_character = (item: string): boolean => {
|
||||
const specialCharacters = ['!','@','#','$',',%','^','&','*','(',')','-','_','=','+','/','*','\\','|','`','~','<','>','.','?'];
|
||||
const specialCharacters = ['!', '@', '#', '$', ',%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '/', '*', '\\', '|', '`', '~', '<', '>', '.', '?'];
|
||||
const hasSpecialChacater = specialCharacters.some((character) => item.includes(character));
|
||||
if(hasSpecialChacater){
|
||||
if (hasSpecialChacater) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const SetPassword = ({}): JSX.Element => {
|
||||
const SetPassword = ({ }): JSX.Element => {
|
||||
const navigate = useNavigate();
|
||||
// see if the user is allowed to come here first
|
||||
const [queryParameters] = useSearchParams()
|
||||
@@ -47,111 +54,119 @@ const SetPassword = ({}): JSX.Element => {
|
||||
const slug = queryParameters.get("slug");
|
||||
|
||||
|
||||
try{
|
||||
try {
|
||||
// make sure it comes back as 200 for a good request. Else go to the homepage
|
||||
axiosInstance.get(`user/set_password/${slug}`)
|
||||
}catch{
|
||||
} catch {
|
||||
navigate('/')
|
||||
}
|
||||
|
||||
const handleSetPassword = ({password1, password2}: PasswordResetValues): void => {
|
||||
try{
|
||||
// verify
|
||||
if(password1 === password2){
|
||||
axiosInstance.post(`user/set_password/${slug}/`, {
|
||||
'password': password1,
|
||||
});
|
||||
const handleSetPassword = ({ password1, password2 }: PasswordResetValues): void => {
|
||||
try {
|
||||
// verify
|
||||
if (password1 === password2) {
|
||||
axiosInstance.post(`user/set_password/${slug}/`, {
|
||||
'password': password1,
|
||||
});
|
||||
|
||||
navigate('/')
|
||||
}
|
||||
}catch(error){
|
||||
console.log('catching the error');
|
||||
<CustomToastMessage message={error as string} />
|
||||
navigate('/')
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('catching the error');
|
||||
<CustomToastMessage message={error as string} />
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column', backgroundImage: `url(${background})`,backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat"}}>
|
||||
<div className='container my-auto'>
|
||||
<div className='row'>
|
||||
<div className='col -lg-4 col-md-8 col-12 mx-auto'>
|
||||
<div className='card z-index-0 fadeIn3 fadeInBottom'>
|
||||
<div className='card-header p-0 position-relative mt-n4 mx-3 z-index-2'>
|
||||
<div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
|
||||
<h4 className='text-white font-weight-bold text-center'>Set your password</h4>
|
||||
</div>
|
||||
</div>
|
||||
return (
|
||||
|
||||
<PageWrapperLayout>
|
||||
<MDBox sx={{
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
bgcolor: 'background.default',
|
||||
position: 'relative'
|
||||
}}>
|
||||
<ParticleBackground />
|
||||
<MDBox sx={{ width: '100%', maxWidth: '400px', zIndex: 1, position: 'relative' }}>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<MDTypography variant="h4" textAlign="center">
|
||||
Set your password
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<div className='card-body text-center'>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSetPassword}
|
||||
validateOnMount
|
||||
validationSchema={validationSchema}>
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleSetPassword}
|
||||
validateOnMount
|
||||
validationSchema={validationSchema}>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomPasswordField
|
||||
label='Password'
|
||||
name="password1"
|
||||
changeHandler={(e) => formik.setFieldValue('password1', e.target.value)} />
|
||||
label='Password'
|
||||
name="password1"
|
||||
changeHandler={(e) => formik.setFieldValue('password1', e.target.value)} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomPasswordField
|
||||
label='Confirm Password'
|
||||
name="password2"
|
||||
changeHandler={(e) => formik.setFieldValue('password2', e.target.value)} />
|
||||
label='Confirm Password'
|
||||
name="password2"
|
||||
changeHandler={(e) => formik.setFieldValue('password2', e.target.value)} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
{formik.values.password1 === formik.values.password2 ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
{formik.values.password1 === formik.values.password2 ? <CheckCircleIcon fontSize='small' color='success' /> : <ErrorIcon fontSize='small' color='warning' />}
|
||||
|
||||
<Typography variant="body1">Passwords Match</Typography>
|
||||
<Typography variant="body1">Passwords Match</Typography>
|
||||
</Stack>
|
||||
</div>
|
||||
<div>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
{formik.values.password1.length > 5 ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
|
||||
<Typography variant="body1">At least 6 characters</Typography>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
{formik.values.password1.length > 5 ? <CheckCircleIcon fontSize='small' color='success' /> : <ErrorIcon fontSize='small' color='warning' />}
|
||||
<Typography variant="body1">At least 6 characters</Typography>
|
||||
</Stack>
|
||||
</div>
|
||||
<div>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
{contains_special_character(formik.values.password1) ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
|
||||
<Typography variant="body1">At least one special character</Typography>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
{contains_special_character(formik.values.password1) ? <CheckCircleIcon fontSize='small' color='success' /> : <ErrorIcon fontSize='small' color='warning' />}
|
||||
<Typography variant="body1">At least one special character</Typography>
|
||||
</Stack>
|
||||
</div>
|
||||
<div>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
<Stack alignItems="center" direction="row" gap={2}>
|
||||
|
||||
{contains_number(formik.values.password1) ? <CheckCircleIcon fontSize='small' color='success'/> : <ErrorIcon fontSize='small' color='warning'/>}
|
||||
<Typography variant="body1">At least one number</Typography>
|
||||
{contains_number(formik.values.password1) ? <CheckCircleIcon fontSize='small' color='success' /> : <ErrorIcon fontSize='small' color='warning' />}
|
||||
<Typography variant="body1">At least one number</Typography>
|
||||
</Stack>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<Button
|
||||
type={'submit'}
|
||||
disabled={!formik.isValid || formik.isSubmitting ||
|
||||
!contains_special_character(formik.values.password1)
|
||||
|| !contains_number(formik.values.password1)
|
||||
}
|
||||
<MDButton
|
||||
type={'submit'}
|
||||
fullWidth
|
||||
disabled={!formik.isValid || formik.isSubmitting ||
|
||||
!contains_special_character(formik.values.password1)
|
||||
|| !contains_number(formik.values.password1)
|
||||
}
|
||||
|
||||
// type={'submit'}
|
||||
// loading={formik.isSubmitting}
|
||||
// disabled={
|
||||
// !formik.isValid || !formik.dirty || formik.isSubmitting
|
||||
// }
|
||||
>
|
||||
Set password
|
||||
</Button>
|
||||
// type={'submit'}
|
||||
// loading={formik.isSubmitting}
|
||||
// disabled={
|
||||
// !formik.isValid || !formik.dirty || formik.isSubmitting
|
||||
// }
|
||||
>
|
||||
Set password
|
||||
</MDButton>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -162,12 +177,11 @@ const SetPassword = ({}): JSX.Element => {
|
||||
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</Card>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
</PageWrapperLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default SetPassword;
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Card, CardContent, Divider, Switch } from "@mui/material";
|
||||
import { Card, CardContent, Divider, Switch, IconButton } from "@mui/material";
|
||||
import MDTypography from "../../ui-kit/components/MDTypography";
|
||||
import MDBox from "../../ui-kit/components/MDBox";
|
||||
import {
|
||||
useMaterialUIController,
|
||||
setDarkMode,
|
||||
setThemeColor,
|
||||
} from "../../ui-kit/context";
|
||||
import palettes from "../../ui-kit/assets/theme/base/palettes";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { axiosInstance } from "../../../axiosApi";
|
||||
@@ -15,73 +17,119 @@ type PreferencesValues = {
|
||||
order: boolean;
|
||||
}
|
||||
|
||||
const ThemeCard = ({}): JSX.Element => {
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
darkMode,
|
||||
} = controller;
|
||||
const ThemeCard = ({ }): JSX.Element => {
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
darkMode,
|
||||
themeColor,
|
||||
} = controller;
|
||||
const themeColors = Object.keys(palettes);
|
||||
|
||||
const [order, setOrder] = useState<boolean>(true);
|
||||
const {updatePreferences} = useContext(PreferenceContext);
|
||||
const [order, setOrder] = useState<boolean>(true);
|
||||
const { updatePreferences } = useContext(PreferenceContext);
|
||||
|
||||
const handleConversationOrder = async ({order}: PreferencesValues): Promise<void> => {
|
||||
try{
|
||||
const {data, }: AxiosResponse<PreferencesType> = await axiosInstance.post('/conversation_preferences', {
|
||||
order: order
|
||||
})
|
||||
updatePreferences();
|
||||
}catch{
|
||||
const handleConversationOrder = async ({ order }: PreferencesValues): Promise<void> => {
|
||||
try {
|
||||
const { data, }: AxiosResponse<PreferencesType> = await axiosInstance.post('/conversation_preferences', {
|
||||
order: order
|
||||
})
|
||||
updatePreferences();
|
||||
} catch {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function getConversationOrder(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<PreferencesType> = await axiosInstance.get(`/conversation_preferences`);
|
||||
setOrder(data.order);
|
||||
async function getConversationOrder() {
|
||||
try {
|
||||
const { data, }: AxiosResponse<PreferencesType> = await axiosInstance.get(`/conversation_preferences`);
|
||||
setOrder(data.order);
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getConversationOrder();
|
||||
}, [])
|
||||
useEffect(() => {
|
||||
getConversationOrder();
|
||||
}, [])
|
||||
|
||||
const handleDarkMode = () => setDarkMode(dispatch, !darkMode);
|
||||
const isThemeReady=false;
|
||||
return(
|
||||
<Card sx={{ mb: 1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Account Preferences
|
||||
const handleDarkMode = () => setDarkMode(dispatch, !darkMode);
|
||||
const isThemeReady = true;
|
||||
return (
|
||||
<Card sx={{ mb: 1 }}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Account Preferences
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
{isThemeReady ? (
|
||||
<MDBox display="flex" justifyContent="space-between" alignItems="center" lineHeight={1}>
|
||||
<MDTypography variant="h6">Light / Dark</MDTypography>
|
||||
<Switch checked={darkMode} onChange={handleDarkMode} disabled={false} />
|
||||
</MDBox>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<MDBox display="flex" justifyContent="space-between" alignItems="center" lineHeight={1}>
|
||||
<MDTypography variant="h6">Converastion Order</MDTypography>
|
||||
<Switch checked={order} onChange={() => {
|
||||
setOrder(!order);
|
||||
handleConversationOrder({order: !order})
|
||||
}} />
|
||||
</MDBox>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<MDBox display="flex" justifyContent="space-between" alignItems="center" lineHeight={1}>
|
||||
<MDTypography variant="h6">Light / Dark</MDTypography>
|
||||
<Switch checked={darkMode} onChange={handleDarkMode} disabled={false} />
|
||||
</MDBox>
|
||||
<MDBox display="flex" justifyContent="space-between" alignItems="center" lineHeight={1} mt={2}>
|
||||
<MDTypography variant="h6">Theme Color</MDTypography>
|
||||
<MDBox>
|
||||
{themeColors.map((color) => (
|
||||
<IconButton
|
||||
key={color}
|
||||
sx={(theme: any) => {
|
||||
const {
|
||||
borders: { borderWidth },
|
||||
palette: { white, dark, background },
|
||||
transitions,
|
||||
} = theme;
|
||||
|
||||
return {
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
padding: 0,
|
||||
border: `${borderWidth[1]} solid ${darkMode ? background.sidenav : white.main}`,
|
||||
borderColor: () => {
|
||||
let borderColorValue = themeColor === color && dark.main;
|
||||
|
||||
if (darkMode && themeColor === color) {
|
||||
borderColorValue = white.main;
|
||||
}
|
||||
|
||||
return borderColorValue;
|
||||
},
|
||||
transition: transitions.create("border-color", {
|
||||
easing: transitions.easing.sharp,
|
||||
duration: transitions.duration.shorter,
|
||||
}),
|
||||
backgroundColor: palettes[color as keyof typeof palettes].main,
|
||||
|
||||
"&:not(:last-child)": {
|
||||
mr: 1,
|
||||
},
|
||||
|
||||
"&:hover, &:focus, &:active": {
|
||||
borderColor: darkMode ? white.main : dark.main,
|
||||
},
|
||||
};
|
||||
}}
|
||||
onClick={() => setThemeColor(dispatch, color)}
|
||||
/>
|
||||
))}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
|
||||
<MDBox display="flex" justifyContent="space-between" alignItems="center" lineHeight={1}>
|
||||
<MDTypography variant="h6">Converastion Order</MDTypography>
|
||||
<Switch checked={order} onChange={() => {
|
||||
setOrder(!order);
|
||||
handleConversationOrder({ order: !order })
|
||||
}} />
|
||||
</MDBox>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default ThemeCard;
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
useMaterialUIController,
|
||||
setDarkMode,
|
||||
setThemeColor,
|
||||
} from "../../ui-kit/context";
|
||||
import palettes from "../../ui-kit/assets/theme/base/palettes";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { axiosInstance } from "../../../axiosApi";
|
||||
import { PreferencesType } from "../../data";
|
||||
import { PreferenceContext } from "../../contexts/PreferencesContext";
|
||||
|
||||
// Styled Components (Reusing/Adapting from Account2/Design System)
|
||||
const GlassCard = styled.div`
|
||||
background: ${({ theme }) => theme.colors.cardBackground};
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
`;
|
||||
|
||||
const CardTitle = styled.h2`
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
padding-bottom: 1rem;
|
||||
`;
|
||||
|
||||
const SettingRow = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const SettingLabel = styled.span`
|
||||
font-size: 1.1rem;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-weight: 500;
|
||||
`;
|
||||
|
||||
const ToggleSwitch = styled.label`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
`;
|
||||
|
||||
const Slider = styled.span`
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'};
|
||||
transition: .4s;
|
||||
border-radius: 24px;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
`;
|
||||
|
||||
const Checkbox = styled.input`
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
&:checked + ${Slider} {
|
||||
background-color: ${({ theme }) => theme.main};
|
||||
}
|
||||
|
||||
&:checked + ${Slider}:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
`;
|
||||
|
||||
const ColorPickerContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
`;
|
||||
|
||||
const ColorButton = styled.button<{ color: string; isSelected: boolean }>`
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: ${(props) => props.color};
|
||||
border: 2px solid ${(props) => (props.isSelected ? props.theme.colors.text : 'transparent')};
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, border-color 0.2s ease;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
`;
|
||||
|
||||
type PreferencesValues = {
|
||||
order: boolean;
|
||||
}
|
||||
|
||||
const ThemeSettingsCard = (): JSX.Element => {
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const {
|
||||
darkMode,
|
||||
themeColor,
|
||||
} = controller;
|
||||
const themeColors = Object.keys(palettes);
|
||||
|
||||
const [order, setOrder] = useState<boolean>(true);
|
||||
const { updatePreferences } = useContext(PreferenceContext);
|
||||
|
||||
const handleConversationOrder = async ({ order }: PreferencesValues): Promise<void> => {
|
||||
try {
|
||||
await axiosInstance.post('/conversation_preferences', {
|
||||
order: order
|
||||
})
|
||||
updatePreferences();
|
||||
} catch {
|
||||
// Error handling
|
||||
}
|
||||
}
|
||||
|
||||
async function getConversationOrder() {
|
||||
try {
|
||||
const { data, }: AxiosResponse<PreferencesType> = await axiosInstance.get(`/conversation_preferences`);
|
||||
setOrder(data.order);
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getConversationOrder();
|
||||
}, [])
|
||||
|
||||
const handleDarkMode = () => setDarkMode(dispatch, !darkMode);
|
||||
|
||||
return (
|
||||
<GlassCard>
|
||||
<CardTitle>Account Preferences</CardTitle>
|
||||
|
||||
<SettingRow>
|
||||
<SettingLabel>Dark Mode</SettingLabel>
|
||||
<ToggleSwitch>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
checked={darkMode}
|
||||
onChange={handleDarkMode}
|
||||
/>
|
||||
<Slider />
|
||||
</ToggleSwitch>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow>
|
||||
<SettingLabel>Theme Color</SettingLabel>
|
||||
<ColorPickerContainer>
|
||||
{themeColors.map((color) => (
|
||||
<ColorButton
|
||||
key={color}
|
||||
color={palettes[color as keyof typeof palettes].main}
|
||||
isSelected={themeColor === color}
|
||||
onClick={() => setThemeColor(dispatch, color)}
|
||||
title={color}
|
||||
/>
|
||||
))}
|
||||
</ColorPickerContainer>
|
||||
</SettingRow>
|
||||
|
||||
<SettingRow>
|
||||
<SettingLabel>Conversation Order (Newest First)</SettingLabel>
|
||||
<ToggleSwitch>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
checked={order}
|
||||
onChange={() => {
|
||||
setOrder(!order);
|
||||
handleConversationOrder({ order: !order })
|
||||
}}
|
||||
/>
|
||||
<Slider />
|
||||
</ToggleSwitch>
|
||||
</SettingRow>
|
||||
|
||||
</GlassCard>
|
||||
);
|
||||
};
|
||||
|
||||
export default ThemeSettingsCard;
|
||||
@@ -1,291 +1,445 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import PageWrapperLayout from "../../components/PageWrapperLayout/PageWrapperLayout";
|
||||
import { AccountContext } from "../../contexts/AccountContext";
|
||||
import MDTypography from "../../ui-kit/components/MDTypography";
|
||||
import { Account, AccountType } from "../../data";
|
||||
import * as Yup from 'yup';
|
||||
import { Card, CardContent, Divider, IconButton, InputAdornment, Paper, Switch, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField } from "@mui/material";
|
||||
import { DeleteForever, Send } from "@mui/icons-material";
|
||||
import { axiosInstance } from "../../../axiosApi";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { ErrorMessage, Field, Form, Formik } from "formik";
|
||||
import MDButton from "../../ui-kit/components/MDButton";
|
||||
import MDBox from "../../ui-kit/components/MDBox";
|
||||
import Header2 from "../../components/Header2/Header2";
|
||||
import Footer from "../../components/Footer/Footer";
|
||||
import ThemeCard from "../../components/ThemeCard/ThemeCard";
|
||||
import ParticleBackground from "../../components/ParticleBackground/ParticleBackground";
|
||||
import styled, { ThemeProvider } from "styled-components";
|
||||
import ThemeSettingsCard from "../../components/ThemeSettingsCard/ThemeSettingsCard";
|
||||
import { useMaterialUIController } from "../../ui-kit/context";
|
||||
import palettes from "../../ui-kit/assets/theme/base/palettes";
|
||||
|
||||
// Styled Components
|
||||
const PageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-family: 'Inter', sans-serif;
|
||||
/* background-color: ${({ theme }) => theme.colors.background}; Removed to show particles */
|
||||
`;
|
||||
|
||||
const ContentWrapper = styled.div`
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 6rem 2rem 2rem 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 5;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
const GlassCard = styled.div`
|
||||
background: ${({ theme }) => theme.colors.cardBackground};
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
`;
|
||||
|
||||
const CardTitle = styled.h2`
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
padding-bottom: 1rem;
|
||||
`;
|
||||
|
||||
const StyledTable = styled.table`
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
const Th = styled.th`
|
||||
text-align: left;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
opacity: 0.7;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const Td = styled.td`
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
width: 100%;
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)'};
|
||||
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.8rem;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:focus {
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'};
|
||||
border-color: ${({ theme }) => theme.main};
|
||||
box-shadow: 0 0 10px ${({ theme }) => theme.main}33; // 33 is approx 20% opacity
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled.button`
|
||||
background: ${({ theme }) => theme.main};
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
color: #fff;
|
||||
padding: 0.8rem 1.5rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
margin-top: 1rem;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px ${({ theme }) => theme.main}66; // 66 is approx 40% opacity
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const ToggleSwitch = styled.label`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
`;
|
||||
|
||||
const Slider = styled.span`
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'};
|
||||
transition: .4s;
|
||||
border-radius: 24px;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
`;
|
||||
|
||||
const Checkbox = styled.input`
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
&:checked + ${Slider} {
|
||||
background-color: ${({ theme }) => theme.main};
|
||||
}
|
||||
|
||||
&:checked + ${Slider}:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
&:disabled + ${Slider} {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
|
||||
const DeleteButton = styled.button`
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #ff6b6b;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #ff4757;
|
||||
}
|
||||
`;
|
||||
|
||||
type AccountUpdateValues = {
|
||||
email: string,
|
||||
email: string,
|
||||
}
|
||||
|
||||
type AccountInformationProps = {
|
||||
account: Account | undefined
|
||||
account: Account | undefined
|
||||
}
|
||||
|
||||
type InviteValues = {
|
||||
email: string
|
||||
email: string
|
||||
}
|
||||
const validationSchema = Yup.object().shape({
|
||||
email: Yup.string().email().required("This is requried")
|
||||
}
|
||||
email: Yup.string().email().required("This is required")
|
||||
}
|
||||
)
|
||||
|
||||
type AddUserCardProps = {
|
||||
getCompanyUsers: ()=> void;
|
||||
getCompanyUsers: () => void;
|
||||
}
|
||||
|
||||
type CompanyAccountLineProps = {
|
||||
user: Account
|
||||
handleUserUpdate: (email: string, field: string, value: string) => void
|
||||
user: Account
|
||||
handleUserUpdate: (email: string, field: string, value: string) => void
|
||||
}
|
||||
|
||||
const CompanyAccountLine = ({user, handleUserUpdate}: CompanyAccountLineProps): JSX.Element => {
|
||||
const {account} = useContext(AccountContext);
|
||||
const CompanyAccountLine = ({ user, handleUserUpdate }: CompanyAccountLineProps): JSX.Element => {
|
||||
const { account } = useContext(AccountContext);
|
||||
|
||||
return(
|
||||
<TableRow key={user.email}>
|
||||
<TableCell><MDTypography> {user.email}</MDTypography></TableCell>
|
||||
<TableCell><MDTypography> {user.first_name}</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>{user.last_name}</MDTypography></TableCell>
|
||||
<TableCell >
|
||||
<Switch checked={user.is_company_manager} onChange={(event) => handleUserUpdate(user.email, 'company_manager', event.target.value)} disabled={account?.email === user.email} />
|
||||
</TableCell>
|
||||
<TableCell >
|
||||
<Switch checked={user.is_active} onChange={(event) => handleUserUpdate(user.email, 'is_active', event.target.value)} />
|
||||
</TableCell>
|
||||
<TableCell >
|
||||
<Switch checked={user.has_password && user.has_signed_tos} onChange={(event) => handleUserUpdate(user.email, 'has_password', event.target.value)} disabled={!user.has_password} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton onClick={() => handleUserUpdate(user.email, 'delete', '')}>
|
||||
<DeleteForever color="warning" />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
|
||||
const AddUserCard = ({getCompanyUsers}: AddUserCardProps): JSX.Element => {
|
||||
const initialValues = {'email': '',}
|
||||
const handleInvite = async ({email}: InviteValues, {resetForm}: any): Promise<void> => {
|
||||
try{
|
||||
await axiosInstance.post('/user/invite/', {
|
||||
'email': email
|
||||
})
|
||||
getCompanyUsers()
|
||||
|
||||
} catch{
|
||||
// put a message here
|
||||
}
|
||||
resetForm();
|
||||
|
||||
}
|
||||
return(
|
||||
<Card sx={{mt:1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Invite A User
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
|
||||
<CardContent>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleInvite}
|
||||
validateOnMount
|
||||
validationSchema={validationSchema}>
|
||||
{(formik) =>
|
||||
<Form>
|
||||
<div className="row">
|
||||
<div className='col-12'>
|
||||
<Field
|
||||
name={"email"}
|
||||
fullWidth
|
||||
as={TextField}
|
||||
label={"Email"}
|
||||
errorstring={<ErrorMessage name={"prompt"}/>}
|
||||
size={'small'}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
margin={"dense"}
|
||||
variant={"outlined"}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position='end'>
|
||||
<MDButton
|
||||
type={'submit'}
|
||||
startIcon={<Send/>}
|
||||
disabled={!formik.isValid || formik.isSubmitting}>
|
||||
<></>
|
||||
|
||||
</MDButton>
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
>
|
||||
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Form>
|
||||
}
|
||||
</Formik>
|
||||
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const CompanyManagerCard = ({}): JSX.Element => {
|
||||
const [users, setUsers] = useState<Account[]>([]);
|
||||
const {account}= useContext(AccountContext);
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
|
||||
const handleUserUpdate = async(email: string, field: string, value: string): Promise<void> => {
|
||||
//console.log(email, field, value)
|
||||
if(field === 'delete'){
|
||||
await axiosInstance.delete(`/company_users`, {
|
||||
data: {'email':email}
|
||||
|
||||
})
|
||||
}else {
|
||||
await axiosInstance.post(`/company_users`, {
|
||||
'email':email,
|
||||
'field': field,
|
||||
'value': value
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// get all of th edata again
|
||||
try{
|
||||
const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
|
||||
setUsers(data.map((item) => new Account({
|
||||
|
||||
email: item.email,
|
||||
first_name: item.first_name,
|
||||
last_name: item.last_name,
|
||||
is_company_manager: item.is_company_manager,
|
||||
has_password: item.has_usable_password,
|
||||
is_active: item.is_active,
|
||||
company: undefined
|
||||
})))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function getCompanyUsers(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
|
||||
setUsers(data.map((item) => new Account({
|
||||
|
||||
email: item.email,
|
||||
first_name: item.first_name,
|
||||
last_name: item.last_name,
|
||||
is_company_manager: item.is_company_manager,
|
||||
has_password: item.has_usable_password,
|
||||
is_active: item.is_active,
|
||||
has_signed_tos: item.has_signed_tos,
|
||||
company: undefined
|
||||
})))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getCompanyUsers();
|
||||
}, [])
|
||||
|
||||
return(
|
||||
<>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Company Accounts
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
|
||||
<CardContent>
|
||||
<TableContainer component={Paper}>
|
||||
<Table >
|
||||
<TableHead sx={{ display: "table-header-group" }}>
|
||||
<TableRow>
|
||||
<TableCell><MDTypography>Email</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>First Name</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Last Name</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Is Manager</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Is Active</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Has Password</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Delete</MDTypography></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{users.map((user) => <CompanyAccountLine user={user} handleUserUpdate={handleUserUpdate} key={user.email}/>)}
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
<AddUserCard getCompanyUsers={getCompanyUsers} />
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const AccountPage =({}): JSX.Element => {
|
||||
const { account } = useContext(AccountContext)
|
||||
if(account?.is_company_manager){
|
||||
|
||||
return(
|
||||
<>
|
||||
<ThemeCard />
|
||||
<CompanyManagerCard />
|
||||
</>
|
||||
|
||||
)
|
||||
|
||||
}else{
|
||||
return(
|
||||
<>
|
||||
<ThemeCard />
|
||||
<MDTypography>Account and prompt information will be available soon</MDTypography>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const Account2 = ({}): JSX.Element => {
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<tr>
|
||||
<Td>{user.email}</Td>
|
||||
<Td>{user.first_name}</Td>
|
||||
<Td>{user.last_name}</Td>
|
||||
<Td>
|
||||
<ToggleSwitch>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
checked={user.is_company_manager}
|
||||
onChange={(event) => handleUserUpdate(user.email, 'company_manager', String(event.target.checked))}
|
||||
disabled={account?.email === user.email}
|
||||
/>
|
||||
<Slider />
|
||||
</ToggleSwitch>
|
||||
</Td>
|
||||
<Td>
|
||||
<ToggleSwitch>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
checked={user.is_active}
|
||||
onChange={(event) => handleUserUpdate(user.email, 'is_active', String(event.target.checked))}
|
||||
/>
|
||||
<Slider />
|
||||
</ToggleSwitch>
|
||||
</Td>
|
||||
<Td>
|
||||
<ToggleSwitch>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
checked={user.has_password && user.has_signed_tos}
|
||||
onChange={(event) => handleUserUpdate(user.email, 'has_password', String(event.target.checked))}
|
||||
disabled={!user.has_password}
|
||||
/>
|
||||
<Slider />
|
||||
</ToggleSwitch>
|
||||
</Td>
|
||||
<Td>
|
||||
<DeleteButton onClick={() => handleUserUpdate(user.email, 'delete', '')}>
|
||||
🗑️
|
||||
</DeleteButton>
|
||||
</Td>
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
|
||||
const AddUserCard = ({ getCompanyUsers }: AddUserCardProps): JSX.Element => {
|
||||
const initialValues = { 'email': '', }
|
||||
const handleInvite = async ({ email }: InviteValues, { resetForm }: any): Promise<void> => {
|
||||
try {
|
||||
await axiosInstance.post('/user/invite/', {
|
||||
'email': email
|
||||
})
|
||||
getCompanyUsers()
|
||||
|
||||
} catch {
|
||||
// put a message here
|
||||
}
|
||||
resetForm();
|
||||
|
||||
}
|
||||
return (
|
||||
<GlassCard>
|
||||
<CardTitle>Invite A User</CardTitle>
|
||||
<Formik
|
||||
initialValues={initialValues}
|
||||
onSubmit={handleInvite}
|
||||
validateOnMount
|
||||
validationSchema={validationSchema}>
|
||||
{(formik) =>
|
||||
<Form>
|
||||
<div style={{ display: 'flex', gap: '1rem', alignItems: 'flex-start' }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Field
|
||||
name={"email"}
|
||||
as={StyledInput}
|
||||
placeholder={"Email Address"}
|
||||
/>
|
||||
<ErrorMessage name="email">
|
||||
{msg => <div style={{ color: '#ff6b6b', marginTop: '0.5rem', fontSize: '0.9rem' }}>{msg}</div>}
|
||||
</ErrorMessage>
|
||||
</div>
|
||||
<StyledButton
|
||||
type={'submit'}
|
||||
disabled={!formik.isValid || formik.isSubmitting}
|
||||
style={{ marginTop: 0 }}
|
||||
>
|
||||
Invite
|
||||
</StyledButton>
|
||||
</div>
|
||||
</Form>
|
||||
}
|
||||
</Formik>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
|
||||
const CompanyManagerCard = ({ }): JSX.Element => {
|
||||
const [users, setUsers] = useState<Account[]>([]);
|
||||
const { account } = useContext(AccountContext);
|
||||
const [showModal, setShowModal] = useState<boolean>(false);
|
||||
|
||||
const handleUserUpdate = async (email: string, field: string, value: string): Promise<void> => {
|
||||
//console.log(email, field, value)
|
||||
if (field === 'delete') {
|
||||
await axiosInstance.delete(`/company_users`, {
|
||||
data: { 'email': email }
|
||||
|
||||
})
|
||||
} else {
|
||||
await axiosInstance.post(`/company_users`, {
|
||||
'email': email,
|
||||
'field': field,
|
||||
'value': value
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
// get all of th edata again
|
||||
try {
|
||||
const { data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
|
||||
setUsers(data.map((item) => new Account({
|
||||
|
||||
email: item.email,
|
||||
first_name: item.first_name,
|
||||
last_name: item.last_name,
|
||||
is_company_manager: item.is_company_manager,
|
||||
has_password: item.has_usable_password,
|
||||
is_active: item.is_active,
|
||||
company: undefined
|
||||
})))
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function getCompanyUsers() {
|
||||
try {
|
||||
const { data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
|
||||
setUsers(data.map((item) => new Account({
|
||||
|
||||
email: item.email,
|
||||
first_name: item.first_name,
|
||||
last_name: item.last_name,
|
||||
is_company_manager: item.is_company_manager,
|
||||
has_password: item.has_usable_password,
|
||||
is_active: item.is_active,
|
||||
has_signed_tos: item.has_signed_tos,
|
||||
company: undefined
|
||||
})))
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getCompanyUsers();
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<GlassCard>
|
||||
<CardTitle>Company Accounts</CardTitle>
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<StyledTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<Th>Email</Th>
|
||||
<Th>First Name</Th>
|
||||
<Th>Last Name</Th>
|
||||
<Th>Is Manager</Th>
|
||||
<Th>Is Active</Th>
|
||||
<Th>Has Password</Th>
|
||||
<Th>Delete</Th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((user) => <CompanyAccountLine user={user} handleUserUpdate={handleUserUpdate} key={user.email} />)}
|
||||
</tbody>
|
||||
</StyledTable>
|
||||
</div>
|
||||
</GlassCard>
|
||||
<AddUserCard getCompanyUsers={getCompanyUsers} />
|
||||
</>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const AccountPage = ({ }): JSX.Element => {
|
||||
const { account } = useContext(AccountContext)
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemeSettingsCard />
|
||||
{account?.is_company_manager ? (
|
||||
<CompanyManagerCard />
|
||||
) : (
|
||||
<GlassCard>
|
||||
<CardTitle>Account Information</CardTitle>
|
||||
<p style={{ color: 'rgba(255,255,255,0.7)' }}>Account and prompt information will be available soon</p>
|
||||
</GlassCard>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const Account2 = ({ }): JSX.Element => {
|
||||
return (
|
||||
<PageContainer>
|
||||
<ParticleBackground />
|
||||
<Header2 />
|
||||
<MDBox sx={{mt:12}}>
|
||||
|
||||
</MDBox>
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<ContentWrapper>
|
||||
<AccountPage />
|
||||
<Footer />
|
||||
</MDBox>
|
||||
|
||||
|
||||
</PageWrapperLayout>
|
||||
</ContentWrapper>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,236 +1,242 @@
|
||||
import { useContext, useEffect, useState } from "react"
|
||||
import Footer from "../../components/Footer/Footer"
|
||||
import Header2 from "../../components/Header2/Header2"
|
||||
import PageWrapperLayout from "../../components/PageWrapperLayout/PageWrapperLayout"
|
||||
import MDBox from "../../ui-kit/components/MDBox"
|
||||
import { AccountContext } from "../../contexts/AccountContext"
|
||||
import { Card, CardContent, Divider } from "@mui/material"
|
||||
import MDTypography from "../../ui-kit/components/MDTypography"
|
||||
import { Area, Bar, BarChart, ComposedChart, Legend, Line, LineChart, Rectangle, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"
|
||||
import { Area, Bar, BarChart, ComposedChart, Legend, Line, ResponsiveContainer, Tooltip, XAxis, YAxis, Rectangle } from "recharts"
|
||||
import { axiosInstance } from "../../../axiosApi"
|
||||
import { AxiosResponse } from "axios"
|
||||
import { AdminAnalytics, AdminAnalyticsType, CompanyUsageAnalytics, CompanyUsageAnalyticsType, UserConversationAnalytics, UserConvesationAnalyticsType, UserPromptAnalytics, UserPromptAnalyticsType } from "../../data"
|
||||
import ParticleBackground from "../../components/ParticleBackground/ParticleBackground"
|
||||
import styled, { ThemeContext } from "styled-components"
|
||||
|
||||
const UserPromptAnalyticsCard = ({}): JSX.Element => {
|
||||
// Styled Components
|
||||
const PageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-family: 'Inter', sans-serif;
|
||||
/* background-color: ${({ theme }) => theme.colors.background}; Removed to show particles */
|
||||
`;
|
||||
|
||||
const ContentWrapper = styled.div`
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 6rem 2rem 2rem 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 5;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
const GlassCard = styled.div`
|
||||
background: ${({ theme }) => theme.colors.cardBackground};
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
`;
|
||||
|
||||
const CardTitle = styled.h2`
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
padding-bottom: 1rem;
|
||||
`;
|
||||
|
||||
const GridContainer = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
|
||||
@media (min-width: 992px) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
`;
|
||||
|
||||
const ChartContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
`;
|
||||
|
||||
const UserPromptAnalyticsCard = ({ }): JSX.Element => {
|
||||
const [data, setData] = useState<UserPromptAnalytics[]>([])
|
||||
const theme = useContext(ThemeContext);
|
||||
|
||||
async function getUserPromptAnalytics(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<UserPromptAnalyticsType[]> = await axiosInstance.get(`/analytics/user_prompts/`);
|
||||
|
||||
async function getUserPromptAnalytics() {
|
||||
try {
|
||||
const { data, }: AxiosResponse<UserPromptAnalyticsType[]> = await axiosInstance.get(`/analytics/user_prompts/`);
|
||||
setData(data)
|
||||
|
||||
|
||||
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
getUserPromptAnalytics();
|
||||
}, [])
|
||||
return (
|
||||
<Card sx={{ mb: 1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Prompt Usage
|
||||
</MDTypography>
|
||||
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<div style={{ width: "100%", height: 600}} >
|
||||
<GlassCard>
|
||||
<CardTitle>Prompt Usage</CardTitle>
|
||||
<ChartContainer>
|
||||
<ResponsiveContainer>
|
||||
<BarChart data={data}>
|
||||
<XAxis />
|
||||
<YAxis />
|
||||
<Legend />
|
||||
<Bar dataKey="you" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue" />}/>
|
||||
<Bar dataKey="others" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
|
||||
<Bar dataKey="all" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
|
||||
<XAxis stroke={theme?.colors.text} />
|
||||
<YAxis stroke={theme?.colors.text} />
|
||||
<Legend wrapperStyle={{ color: theme?.colors.text }} />
|
||||
<Tooltip contentStyle={{ backgroundColor: theme?.colors.cardBackground, border: `1px solid ${theme?.colors.cardBorder}`, color: theme?.colors.text }} />
|
||||
<Bar dataKey="you" fill={theme?.main || "#667eea"} activeBar={<Rectangle fill={theme?.main ? theme.main + "99" : "#764ba2"} stroke={theme?.colors.text} />} />
|
||||
<Bar dataKey="others" fill="#82ca9d" activeBar={<Rectangle fill="#a8e6cf" stroke={theme?.colors.text} />} />
|
||||
<Bar dataKey="all" fill="#ffb7b2" activeBar={<Rectangle fill="#ffdac1" stroke={theme?.colors.text} />} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChartContainer>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
|
||||
const UserConversationAnalyticsCard = ({}): JSX.Element => {
|
||||
const UserConversationAnalyticsCard = ({ }): JSX.Element => {
|
||||
const [data, setData] = useState<UserConversationAnalytics[]>([])
|
||||
async function getUserConversationAnalytics(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<UserConvesationAnalyticsType[]> = await axiosInstance.get(`/analytics/user_conversations/`);
|
||||
const theme = useContext(ThemeContext);
|
||||
|
||||
async function getUserConversationAnalytics() {
|
||||
try {
|
||||
const { data, }: AxiosResponse<UserConvesationAnalyticsType[]> = await axiosInstance.get(`/analytics/user_conversations/`);
|
||||
setData(data)
|
||||
|
||||
|
||||
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
getUserConversationAnalytics();
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Card sx={{ mb: 1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Conversation Usage
|
||||
</MDTypography>
|
||||
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<div style={{ width: "100%", height: 600}} >
|
||||
<GlassCard>
|
||||
<CardTitle>Conversation Usage</CardTitle>
|
||||
<ChartContainer>
|
||||
<ResponsiveContainer>
|
||||
|
||||
|
||||
<BarChart data={data}>
|
||||
<XAxis />
|
||||
<YAxis />
|
||||
<Legend />
|
||||
<Bar dataKey="you" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue" />}/>
|
||||
<Bar dataKey="others" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
|
||||
<Bar dataKey="all" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
|
||||
<XAxis stroke={theme?.colors.text} />
|
||||
<YAxis stroke={theme?.colors.text} />
|
||||
<Legend wrapperStyle={{ color: theme?.colors.text }} />
|
||||
<Tooltip contentStyle={{ backgroundColor: theme?.colors.cardBackground, border: `1px solid ${theme?.colors.cardBorder}`, color: theme?.colors.text }} />
|
||||
<Bar dataKey="you" fill={theme?.main || "#667eea"} activeBar={<Rectangle fill={theme?.main ? theme.main + "99" : "#764ba2"} stroke={theme?.colors.text} />} />
|
||||
<Bar dataKey="others" fill="#82ca9d" activeBar={<Rectangle fill="#a8e6cf" stroke={theme?.colors.text} />} />
|
||||
<Bar dataKey="all" fill="#ffb7b2" activeBar={<Rectangle fill="#ffdac1" stroke={theme?.colors.text} />} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChartContainer>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
|
||||
const CompanyUsageAnalyticsCard = ({}): JSX.Element => {
|
||||
const CompanyUsageAnalyticsCard = ({ }): JSX.Element => {
|
||||
const [data, setData] = useState<CompanyUsageAnalytics[]>([])
|
||||
async function getCompanyUsageAnalytics(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<CompanyUsageAnalyticsType[]> = await axiosInstance.get(`/analytics/company_usage/`);
|
||||
const theme = useContext(ThemeContext);
|
||||
|
||||
async function getCompanyUsageAnalytics() {
|
||||
try {
|
||||
const { data, }: AxiosResponse<CompanyUsageAnalyticsType[]> = await axiosInstance.get(`/analytics/company_usage/`);
|
||||
setData(data)
|
||||
|
||||
|
||||
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
getCompanyUsageAnalytics();
|
||||
}, [])
|
||||
// const exampleData = [
|
||||
// {
|
||||
// month: 'Feb',
|
||||
// used: 10,
|
||||
// not_used: 5
|
||||
// },
|
||||
// {
|
||||
// month: 'Mar',
|
||||
// used: 7,
|
||||
// not_used: 8
|
||||
// },
|
||||
// {
|
||||
// month: 'Apr',
|
||||
// used: 15,
|
||||
// not_used: 0
|
||||
// },
|
||||
// ]
|
||||
return (
|
||||
<Card sx={{ mb: 1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Account Usage
|
||||
</MDTypography>
|
||||
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<div style={{ width: "100%", height: 600}} >
|
||||
<ResponsiveContainer>
|
||||
<BarChart data={data}>
|
||||
<XAxis dataKey="month"/>
|
||||
<YAxis />
|
||||
<Legend />
|
||||
<Tooltip />
|
||||
<Bar dataKey="used" fill="#8884d8" activeBar={<Rectangle fill="pink" stroke="blue" />}/>
|
||||
<Bar dataKey="not_used" fill="#82ca9d" activeBar={<Rectangle fill="gold" stroke="purple" />}/>
|
||||
</BarChart>
|
||||
return (
|
||||
<GlassCard>
|
||||
<CardTitle>Account Usage</CardTitle>
|
||||
<ChartContainer>
|
||||
<ResponsiveContainer>
|
||||
<BarChart data={data}>
|
||||
<XAxis dataKey="month" stroke={theme?.colors.text} />
|
||||
<YAxis stroke={theme?.colors.text} />
|
||||
<Legend wrapperStyle={{ color: theme?.colors.text }} />
|
||||
<Tooltip contentStyle={{ backgroundColor: theme?.colors.cardBackground, border: `1px solid ${theme?.colors.cardBorder}`, color: theme?.colors.text }} />
|
||||
<Bar dataKey="used" fill={theme?.main || "#667eea"} activeBar={<Rectangle fill={theme?.main ? theme.main + "99" : "#764ba2"} stroke={theme?.colors.text} />} />
|
||||
<Bar dataKey="not_used" fill="#82ca9d" activeBar={<Rectangle fill="#a8e6cf" stroke={theme?.colors.text} />} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChartContainer>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
|
||||
const AdminAnalyticsCard = ({}): JSX.Element => {
|
||||
const AdminAnalyticsCard = ({ }): JSX.Element => {
|
||||
const [data, setData] = useState<AdminAnalytics[]>([])
|
||||
async function getAdminAnalytics(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<AdminAnalyticsType[]> = await axiosInstance.get(`/analytics/admin/`);
|
||||
const theme = useContext(ThemeContext);
|
||||
|
||||
async function getAdminAnalytics() {
|
||||
try {
|
||||
const { data, }: AxiosResponse<AdminAnalyticsType[]> = await axiosInstance.get(`/analytics/admin/`);
|
||||
setData(data)
|
||||
|
||||
|
||||
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
getAdminAnalytics();
|
||||
}, [])
|
||||
return (
|
||||
<Card sx={{ mb: 1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Response Times
|
||||
</MDTypography>
|
||||
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<div style={{ width: "100%", height: 600}} >
|
||||
<ResponsiveContainer>
|
||||
<ComposedChart data={data}>
|
||||
<XAxis dataKey="month"/>
|
||||
<YAxis />
|
||||
<Legend />
|
||||
<Tooltip />
|
||||
<Area
|
||||
dataKey="range"
|
||||
dot={false}
|
||||
connectNulls
|
||||
activeDot={false}
|
||||
/>
|
||||
<Line type="natural" dataKey="avg" connectNulls />
|
||||
<Line type="monotone" dataKey="median" connectNulls />
|
||||
</ComposedChart>
|
||||
<GlassCard>
|
||||
<CardTitle>Response Times</CardTitle>
|
||||
<ChartContainer>
|
||||
<ResponsiveContainer>
|
||||
<ComposedChart data={data}>
|
||||
<XAxis dataKey="month" stroke={theme?.colors.text} />
|
||||
<YAxis stroke={theme?.colors.text} />
|
||||
<Legend wrapperStyle={{ color: theme?.colors.text }} />
|
||||
<Tooltip contentStyle={{ backgroundColor: theme?.colors.cardBackground, border: `1px solid ${theme?.colors.cardBorder}`, color: theme?.colors.text }} />
|
||||
<Area
|
||||
dataKey="range"
|
||||
dot={false}
|
||||
connectNulls
|
||||
activeDot={false}
|
||||
fill="#8884d8"
|
||||
stroke="#8884d8"
|
||||
/>
|
||||
<Line type="natural" dataKey="avg" connectNulls stroke="#ff7300" />
|
||||
<Line type="monotone" dataKey="median" connectNulls stroke="#387908" />
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</ChartContainer>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const AnalyticsInner =({}): JSX.Element => {
|
||||
const AnalyticsInner = ({ }): JSX.Element => {
|
||||
const { account } = useContext(AccountContext)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="row">
|
||||
<div className='col-6 col-xs-12'>
|
||||
<GridContainer>
|
||||
<UserConversationAnalyticsCard />
|
||||
|
||||
</div>
|
||||
<div className='col-6 col-xs-12'>
|
||||
<UserPromptAnalyticsCard />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</GridContainer>
|
||||
|
||||
{account?.is_company_manager ? <CompanyUsageAnalyticsCard /> : <></>}
|
||||
{account?.email === "ryan+admin@aimloperations.com" ? <AdminAnalyticsCard /> : <></>}
|
||||
@@ -239,18 +245,15 @@ const AnalyticsInner =({}): JSX.Element => {
|
||||
)
|
||||
}
|
||||
|
||||
const AnalyticsPage = ({}): JSX.Element => {
|
||||
const AnalyticsPage = ({ }): JSX.Element => {
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<PageContainer>
|
||||
<ParticleBackground />
|
||||
<Header2 />
|
||||
<MDBox sx={{mt:12}}>
|
||||
|
||||
</MDBox>
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<ContentWrapper>
|
||||
<AnalyticsInner />
|
||||
<Footer />
|
||||
</MDBox>
|
||||
</PageWrapperLayout>
|
||||
</ContentWrapper>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,84 +1,205 @@
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
Divider,
|
||||
InputAdornment,
|
||||
MenuItem,
|
||||
Select,
|
||||
} from "@mui/material";
|
||||
|
||||
import DashboardLayout from "../../ui-kit/examples/LayoutContainers/DashboardLayout";
|
||||
|
||||
import MDBox from "../../ui-kit/components/MDBox";
|
||||
import { useMaterialUIController } from "../../ui-kit/context";
|
||||
import { useContext, useEffect, useRef, useState } from "react";
|
||||
|
||||
// Images
|
||||
import MDTypography from "../../ui-kit/components/MDTypography";
|
||||
import { CardFooter } from "react-bootstrap";
|
||||
import MDInput from "../../ui-kit/components/MDInput";
|
||||
import { ErrorMessage, Field, Form, Formik } from "formik";
|
||||
import MDButton from "../../ui-kit/components/MDButton";
|
||||
import { AttachFile, Send } from "@mui/icons-material";
|
||||
import Header2 from "../../components/Header2/Header2";
|
||||
import DashboardWrapperLayout from "../../components/DashboardWrapperLayout/DashboardWrapperLayout";
|
||||
import React, { useContext, useEffect, useRef, useState } from "react";
|
||||
import styled, { ThemeContext } from "styled-components";
|
||||
import { Formik, Form, Field, ErrorMessage } from "formik";
|
||||
import * as Yup from "yup";
|
||||
import { AttachFile, Send } from "@mui/icons-material"; // Keeping icons for now, can replace later if needed
|
||||
import Markdown from "markdown-to-jsx";
|
||||
|
||||
import {
|
||||
Announcement,
|
||||
AnnouncementType,
|
||||
Conversation,
|
||||
ConversationPrompt,
|
||||
ConversationPromptType,
|
||||
ConversationType,
|
||||
} from "../../data";
|
||||
import { axiosInstance } from "../../../axiosApi";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { ConversationContext } from "../../contexts/ConversationContext";
|
||||
import Markdown from "markdown-to-jsx";
|
||||
import ConversationDetailCard from "../../components/ConversationDetailCard/ConversationDetailCard";
|
||||
import { WebSocketContext } from "../../contexts/WebSocketContext";
|
||||
import { AccountContext } from "../../contexts/AccountContext";
|
||||
import { styled } from "@mui/material/styles";
|
||||
import Footer from "../../components/Footer/Footer";
|
||||
import { MessageContext } from "../../contexts/MessageContext";
|
||||
import CustomSelect, {
|
||||
CustomSelectItem,
|
||||
} from "../../components/CustomSelect/CustomSelect";
|
||||
import ParticleBackground from "../../components/ParticleBackground/ParticleBackground";
|
||||
|
||||
const MODELS = ["Turbo", "RAG"];
|
||||
import Header2 from "../../components/Header2/Header2";
|
||||
|
||||
type RenderMessageProps = {
|
||||
response: string;
|
||||
index: number;
|
||||
};
|
||||
// Styled Components
|
||||
const PageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-family: 'Inter', sans-serif;
|
||||
/* background-color: ${({ theme }) => theme.colors.background}; Removed to show particles */
|
||||
`;
|
||||
|
||||
type AsyncChatProps = {
|
||||
selectedConversation: number | undefined;
|
||||
conversationTitle: string;
|
||||
conversations: Conversation[];
|
||||
setConversations: React.Dispatch<React.SetStateAction<Conversation[]>>;
|
||||
setSelectedConversation: React.Dispatch<
|
||||
React.SetStateAction<number | undefined>
|
||||
>;
|
||||
drawerWidth: number;
|
||||
};
|
||||
const Sidebar = styled.div`
|
||||
width: 280px;
|
||||
height: 100%;
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(0, 0, 0, 0.6)' : 'rgba(255, 255, 255, 0.6)'};
|
||||
backdrop-filter: blur(10px);
|
||||
border-right: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
padding-top: 5rem; // Account for header
|
||||
z-index: 10;
|
||||
transition: transform 0.3s ease;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
position: absolute;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
`;
|
||||
|
||||
const MainContent = styled.div`
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
z-index: 5;
|
||||
padding-top: 4rem; // Account for header
|
||||
`;
|
||||
|
||||
const ChatArea = styled.div`
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
const InputArea = styled.div`
|
||||
padding: 1.5rem 2rem;
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.4)'};
|
||||
backdrop-filter: blur(5px);
|
||||
border-top: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
`;
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
position: relative;
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)'};
|
||||
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
border-radius: 1.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:focus-within {
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'};
|
||||
border-color: ${({ theme }) => theme.main};
|
||||
box-shadow: 0 0 15px ${({ theme }) => theme.main}33;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-size: 1rem;
|
||||
padding: 0.8rem;
|
||||
outline: none;
|
||||
|
||||
&::placeholder {
|
||||
color: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.4)' : 'rgba(0, 0, 0, 0.4)'};
|
||||
}
|
||||
`;
|
||||
|
||||
const IconButton = styled.button`
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.6)' : 'rgba(0, 0, 0, 0.6)'};
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
|
||||
const ConversationItem = styled.div<{ $active: boolean }>`
|
||||
padding: 0.8rem 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
background: ${(props) => (props.$active ? props.theme.main + "33" : "transparent")};
|
||||
color: ${(props) => (props.$active ? props.theme.colors.text : props.theme.darkMode ? "rgba(255, 255, 255, 0.7)" : "rgba(0, 0, 0, 0.7)")};
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border: 1px solid ${(props) => (props.$active ? props.theme.main + "66" : "transparent")};
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'};
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
}
|
||||
`;
|
||||
|
||||
const NewChatButton = styled.button`
|
||||
width: 100%;
|
||||
padding: 0.8rem;
|
||||
margin-bottom: 1rem;
|
||||
background: ${({ theme }) => theme.main};
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px ${({ theme }) => theme.main}66;
|
||||
}
|
||||
`;
|
||||
|
||||
const VisuallyHiddenInput = styled.input`
|
||||
clip: rect(0 0 0 0);
|
||||
clip-path: inset(50%);
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
white-space: nowrap;
|
||||
width: 1px;
|
||||
`;
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
prompt: Yup.string()
|
||||
.min(1, "Need to have at least one character")
|
||||
.required("This is requried"),
|
||||
});
|
||||
|
||||
const VisuallyHiddenInput = styled("input")({
|
||||
clip: "rect(0 0 0 0)",
|
||||
clipPath: "inset(50%)",
|
||||
height: 1,
|
||||
overflow: "hidden",
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
whiteSpace: "nowrap",
|
||||
width: 1,
|
||||
.required("This is required"),
|
||||
});
|
||||
|
||||
type PromptValues = {
|
||||
@@ -88,37 +209,18 @@ type PromptValues = {
|
||||
modelName: string;
|
||||
};
|
||||
|
||||
const models: CustomSelectItem[] = [
|
||||
{
|
||||
label: "General",
|
||||
value: "GENERAL",
|
||||
},
|
||||
{
|
||||
label: "Code",
|
||||
value: "CODE",
|
||||
},
|
||||
{
|
||||
label: "Reasoning",
|
||||
value: "REASONING",
|
||||
},
|
||||
];
|
||||
|
||||
const AlwaysScrollToBottom = (): JSX.Element => {
|
||||
const elementRef = useRef<any>();
|
||||
useEffect(() => elementRef.current.scrollIntoView());
|
||||
useEffect(() => elementRef.current?.scrollIntoView({ behavior: "smooth" }));
|
||||
return <div ref={elementRef} />;
|
||||
};
|
||||
|
||||
const AsyncDashboardInner = ({}): JSX.Element => {
|
||||
const [controller, dispatch] = useMaterialUIController();
|
||||
const { darkMode } = controller;
|
||||
const buttonColor = darkMode ? "dark" : "light";
|
||||
const AsyncDashboardInner = ({ }): JSX.Element => {
|
||||
const [announcements, setAnnouncement] = useState<Announcement[]>([]);
|
||||
const [subscribe, unsubscribe, socket, sendMessage] =
|
||||
useContext(WebSocketContext);
|
||||
const { account } = useContext(AccountContext);
|
||||
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const { conversations, selectedConversation, setSelectedConversation } =
|
||||
useContext(ConversationContext);
|
||||
|
||||
@@ -130,6 +232,7 @@ const AsyncDashboardInner = ({}): JSX.Element => {
|
||||
} = useContext(MessageContext);
|
||||
|
||||
const conversationRef = useRef(conversationDetails);
|
||||
const theme = useContext(ThemeContext);
|
||||
|
||||
async function GetAnnouncements() {
|
||||
const response: AxiosResponse<AnnouncementType[]> =
|
||||
@@ -145,16 +248,10 @@ const AsyncDashboardInner = ({}): JSX.Element => {
|
||||
);
|
||||
}
|
||||
|
||||
const conversationTitle =
|
||||
conversations.find((item) => item.id === selectedConversation)?.title ??
|
||||
"New Conversation";
|
||||
const colorName = darkMode ? "light" : "dark";
|
||||
|
||||
const handlePromptSubmit = async (
|
||||
{ prompt, file, fileType, modelName }: PromptValues,
|
||||
{ resetForm }: any,
|
||||
): Promise<void> => {
|
||||
// send the prompt to be saved
|
||||
try {
|
||||
const tempConversations: ConversationPrompt[] = [
|
||||
...conversationDetails,
|
||||
@@ -163,146 +260,140 @@ const AsyncDashboardInner = ({}): JSX.Element => {
|
||||
];
|
||||
|
||||
conversationRef.current = tempConversations;
|
||||
|
||||
setConversationDetails(tempConversations);
|
||||
// TODO: add the file here
|
||||
|
||||
sendMessage(prompt, selectedConversation, file, fileType, modelName);
|
||||
resetForm();
|
||||
} catch (e) {
|
||||
console.log(`error ${e}`);
|
||||
// TODO: make this user friendly
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<PageContainer>
|
||||
<ParticleBackground />
|
||||
<Header2 />
|
||||
<MDBox sx={{ mt: 5 }}></MDBox>1
|
||||
<MDBox
|
||||
sx={{
|
||||
margin: "0 auto",
|
||||
width: "80%",
|
||||
height: "80%",
|
||||
minHeight: "80%",
|
||||
maxHeight: "80%",
|
||||
align: "center",
|
||||
}}
|
||||
>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">{conversationTitle}</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardContent>
|
||||
<>
|
||||
{conversationDetails.length > 0 ? (
|
||||
conversationDetails.map((convo_detail) =>
|
||||
convo_detail.message.length > 0 ? (
|
||||
<ConversationDetailCard
|
||||
message={convo_detail.message}
|
||||
user_created={convo_detail.user_created}
|
||||
key={convo_detail.id}
|
||||
/>
|
||||
) : (
|
||||
<ConversationDetailCard
|
||||
message={stateMessage}
|
||||
user_created={convo_detail.user_created}
|
||||
key={convo_detail.id}
|
||||
/>
|
||||
),
|
||||
)
|
||||
) : (
|
||||
<Markdown className="text-center" color="inherit">
|
||||
Either select a previous conversation on start a new one.
|
||||
</Markdown>
|
||||
)}
|
||||
<AlwaysScrollToBottom />
|
||||
</>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<CardFooter>
|
||||
<Formik
|
||||
initialValues={{
|
||||
prompt: "",
|
||||
file: null,
|
||||
fileType: null,
|
||||
modelName: "",
|
||||
}}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handlePromptSubmit}
|
||||
>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
<Field
|
||||
name={"prompt"}
|
||||
fullWidth
|
||||
as={MDInput}
|
||||
label={"Prompt"}
|
||||
errorstring={<ErrorMessage name={"prompt"} />}
|
||||
size={"large"}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
variant={"outlined"}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<MDButton
|
||||
component="label"
|
||||
startIcon={<AttachFile />}
|
||||
role={undefined}
|
||||
tabIndex={-1}
|
||||
color={formik.values.file ? "dark" : "light"}
|
||||
>
|
||||
<VisuallyHiddenInput
|
||||
type="file"
|
||||
accept=".csv,.xlsx,.txt"
|
||||
onChange={(event) => {
|
||||
const file = event.target.files?.[0];
|
||||
//console.log(file)
|
||||
if (file) {
|
||||
formik.setFieldValue("file", file);
|
||||
formik.setFieldValue("fileType", file.type);
|
||||
} else {
|
||||
formik.setFieldValue("file", null);
|
||||
formik.setFieldValue("fileType", null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</MDButton>
|
||||
<MDButton
|
||||
type={"submit"}
|
||||
startIcon={<Send />}
|
||||
disabled={!formik.isValid}
|
||||
color={buttonColor}
|
||||
>
|
||||
<></>
|
||||
</MDButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
></Field>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
|
||||
{/* <MDInput
|
||||
label="Prompt"
|
||||
/> */}
|
||||
</CardFooter>
|
||||
</Card>
|
||||
<Footer />
|
||||
</MDBox>
|
||||
</DashboardLayout>
|
||||
<Sidebar>
|
||||
<NewChatButton onClick={() => setSelectedConversation(undefined)}>
|
||||
+ New Chat
|
||||
</NewChatButton>
|
||||
<div style={{ overflowY: 'auto', flex: 1 }}>
|
||||
{conversations.map((convo) => (
|
||||
<ConversationItem
|
||||
key={convo.id}
|
||||
$active={convo.id === selectedConversation}
|
||||
onClick={() => setSelectedConversation(convo.id)}
|
||||
>
|
||||
{convo.title || "New Conversation"}
|
||||
</ConversationItem>
|
||||
))}
|
||||
</div>
|
||||
</Sidebar>
|
||||
|
||||
<MainContent>
|
||||
<ChatArea>
|
||||
{conversationDetails.length > 0 ? (
|
||||
conversationDetails.map((convo_detail, index) =>
|
||||
convo_detail.message.length > 0 ? (
|
||||
<ConversationDetailCard
|
||||
message={convo_detail.message}
|
||||
user_created={convo_detail.user_created}
|
||||
key={convo_detail.id || index}
|
||||
/>
|
||||
) : (
|
||||
<ConversationDetailCard
|
||||
message={stateMessage}
|
||||
user_created={convo_detail.user_created}
|
||||
key={convo_detail.id || index}
|
||||
/>
|
||||
)
|
||||
)
|
||||
) : (
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
color: theme?.darkMode ? 'rgba(255,255,255,0.5)' : 'rgba(0,0,0,0.5)',
|
||||
fontSize: '1.2rem'
|
||||
}}>
|
||||
<Markdown>Select a conversation or start a new one.</Markdown>
|
||||
</div>
|
||||
)}
|
||||
<AlwaysScrollToBottom />
|
||||
</ChatArea>
|
||||
|
||||
<InputArea>
|
||||
<Formik
|
||||
initialValues={{
|
||||
prompt: "",
|
||||
file: null,
|
||||
fileType: null,
|
||||
modelName: "",
|
||||
}}
|
||||
validationSchema={validationSchema}
|
||||
onSubmit={handlePromptSubmit}
|
||||
>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
<StyledInputContainer>
|
||||
<label htmlFor="file-upload" style={{ cursor: 'pointer', display: 'flex' }}>
|
||||
<IconButton as="span">
|
||||
<AttachFile />
|
||||
</IconButton>
|
||||
</label>
|
||||
<VisuallyHiddenInput
|
||||
id="file-upload"
|
||||
type="file"
|
||||
accept=".csv,.xlsx,.txt"
|
||||
onChange={(event) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
formik.setFieldValue("file", file);
|
||||
formik.setFieldValue("fileType", file.type);
|
||||
} else {
|
||||
formik.setFieldValue("file", null);
|
||||
formik.setFieldValue("fileType", null);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<Field
|
||||
name="prompt"
|
||||
as={StyledInput}
|
||||
placeholder="Type a message..."
|
||||
autoComplete="off"
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
type="submit"
|
||||
disabled={!formik.isValid || !formik.dirty}
|
||||
style={{ color: formik.isValid && formik.dirty ? '#fff' : undefined, background: formik.isValid && formik.dirty ? theme?.main : undefined }}
|
||||
>
|
||||
<Send />
|
||||
</IconButton>
|
||||
</StyledInputContainer>
|
||||
{formik.values.file && (
|
||||
<div style={{
|
||||
marginTop: '0.5rem',
|
||||
fontSize: '0.8rem',
|
||||
color: theme?.darkMode ? 'rgba(255,255,255,0.6)' : 'rgba(0,0,0,0.6)',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
Attached: {(formik.values.file as File).name}
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</InputArea>
|
||||
</MainContent>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const AsyncDashboard2 = ({}): JSX.Element => {
|
||||
return (
|
||||
<DashboardWrapperLayout>
|
||||
<AsyncDashboardInner />
|
||||
</DashboardWrapperLayout>
|
||||
);
|
||||
const AsyncDashboard2 = ({ }): JSX.Element => {
|
||||
return <AsyncDashboardInner />;
|
||||
};
|
||||
|
||||
export default AsyncDashboard2;
|
||||
|
||||
@@ -1,200 +1,267 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import MDBox from "../../ui-kit/components/MDBox";
|
||||
import { Box, Button, Card, CardContent, Divider, IconButton, Paper, Switch, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
|
||||
import { Document, DocumentType } from "../../data";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { axiosInstance } from "../../../axiosApi";
|
||||
import Header2 from "../../components/Header2/Header2";
|
||||
import PageWrapperLayout from "../../components/PageWrapperLayout/PageWrapperLayout";
|
||||
import Footer from "../../components/Footer/Footer";
|
||||
import MDTypography from "../../ui-kit/components/MDTypography";
|
||||
import FeedbackSubmitCard from "../../components/FeedbackSubmitCard/FeedbackSubmitCard";
|
||||
import { CheckCircle, CloudUpload, DeleteForever, Pending } from "@mui/icons-material";
|
||||
import { Formik } from "formik";
|
||||
import ParticleBackground from "../../components/ParticleBackground/ParticleBackground";
|
||||
import styled from "styled-components";
|
||||
import MDButton from "../../ui-kit/components/MDButton";
|
||||
import PaginatedTable from "../../components/PaginatedTable/PaginatedTable";
|
||||
|
||||
type DocumentLineProps = {
|
||||
document: Document,
|
||||
handleDocumentUpdate: (document_id: number, value: string) => void
|
||||
}
|
||||
// Styled Components
|
||||
const PageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-family: 'Inter', sans-serif;
|
||||
/* background-color: ${({ theme }) => theme.colors.background}; Removed to show particles */
|
||||
`;
|
||||
|
||||
const ContentWrapper = styled.div`
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 6rem 2rem 2rem 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 5;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
const GlassCard = styled.div`
|
||||
background: ${({ theme }) => theme.colors.cardBackground};
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
`;
|
||||
|
||||
const CardTitle = styled.h2`
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
padding-bottom: 1rem;
|
||||
`;
|
||||
|
||||
const StyledTable = styled.table`
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
const Th = styled.th`
|
||||
text-align: left;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
opacity: 0.7;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const Td = styled.td`
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
`;
|
||||
|
||||
const StyledButton = styled.button`
|
||||
background: ${({ theme }) => theme.main};
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
color: #fff;
|
||||
padding: 0.8rem 1.5rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px ${({ theme }) => theme.main}66;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const FileInputLabel = styled.label`
|
||||
background: ${({ theme }) => theme.main};
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
color: #fff;
|
||||
padding: 0.8rem 1.5rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-right: 1rem;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px ${({ theme }) => theme.main}66;
|
||||
}
|
||||
`;
|
||||
|
||||
const StatusIcon = styled.span<{ status: 'success' | 'pending' }>`
|
||||
color: ${props => props.status === 'success' ? '#4caf50' : '#bdbdbd'};
|
||||
font-size: 1.5rem;
|
||||
`;
|
||||
|
||||
const ToggleSwitch = styled.label`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
`;
|
||||
|
||||
const Slider = styled.span`
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'};
|
||||
transition: .4s;
|
||||
border-radius: 24px;
|
||||
|
||||
&:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
`;
|
||||
|
||||
const Checkbox = styled.input`
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
&:checked + ${Slider} {
|
||||
background-color: ${({ theme }) => theme.main};
|
||||
}
|
||||
|
||||
&:checked + ${Slider}:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
&:disabled + ${Slider} {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
`;
|
||||
|
||||
|
||||
type DocumentTableCardProps = {
|
||||
documents: Document[],
|
||||
setDocuments: React.Dispatch<React.SetStateAction<Document[]>>
|
||||
documents: Document[],
|
||||
setDocuments: React.Dispatch<React.SetStateAction<Document[]>>
|
||||
}
|
||||
|
||||
const DocumentStorageTableRow = ({document, handleDocumentUpdate}: DocumentLineProps): JSX.Element =>
|
||||
{
|
||||
return(
|
||||
<TableRow key={document.id}>
|
||||
<TableCell><MDTypography> {document.name}</MDTypography></TableCell>
|
||||
<TableCell><MDTypography> {document.date_uploaded}</MDTypography></TableCell>
|
||||
const CompanyDocumentStorageTableCard = ({ documents, setDocuments }: DocumentTableCardProps): JSX.Element => {
|
||||
|
||||
<TableCell>
|
||||
{ document.processed ? (
|
||||
<IconButton onClick={() => console.log('clicked')}>
|
||||
<CheckCircle color="success" />
|
||||
</IconButton>
|
||||
async function getUploadedDocuments() {
|
||||
try {
|
||||
const { data, }: AxiosResponse<DocumentType[]> = await axiosInstance.get(`/documents/`);
|
||||
setDocuments(data.map((item) => new Document({
|
||||
|
||||
):(
|
||||
<IconButton onClick={() => console.log('clicked')}>
|
||||
<Pending color="disabled" />
|
||||
</IconButton>
|
||||
id: item.id,
|
||||
name: item.file.replace(/^.*[\\\/]/, ''),
|
||||
date_uploaded: item.created.substring(0, 10),
|
||||
active: item.active,
|
||||
processed: item.processed,
|
||||
|
||||
)}
|
||||
})))
|
||||
|
||||
</TableCell>
|
||||
<TableCell >
|
||||
<Switch checked={document.active} disabled={true} onChange={(event) => handleDocumentUpdate(document.id, event.target.value)} />
|
||||
</TableCell>
|
||||
|
||||
</TableRow>
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getUploadedDocuments();
|
||||
}, [])
|
||||
return (
|
||||
<GlassCard>
|
||||
<CardTitle>Your documents in the company workspace</CardTitle>
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<StyledTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Date Uploaded</Th>
|
||||
<Th>Processed</Th>
|
||||
<Th>Active</Th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{documents.map((doc) => (
|
||||
<tr key={doc.id}>
|
||||
<Td>{doc.name}</Td>
|
||||
<Td>{doc.date_uploaded}</Td>
|
||||
<Td>
|
||||
{doc.processed ? (
|
||||
<StatusIcon status="success">✓</StatusIcon>
|
||||
) : (
|
||||
<StatusIcon status="pending">⏳</StatusIcon>
|
||||
)}
|
||||
</Td>
|
||||
<Td>
|
||||
<ToggleSwitch>
|
||||
<Checkbox
|
||||
type="checkbox"
|
||||
checked={doc.active}
|
||||
disabled={true}
|
||||
/>
|
||||
<Slider />
|
||||
</ToggleSwitch>
|
||||
</Td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</StyledTable>
|
||||
</div>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
|
||||
const CompanyDocumentStorageTableCard =({documents, setDocuments}: DocumentTableCardProps): JSX.Element => {
|
||||
|
||||
const handleDocumentUpdate = async(document_id: number, value: string): Promise<void> => {
|
||||
console.log(document_id, value)
|
||||
// if(field === 'delete'){
|
||||
// await axiosInstance.delete(`/company_users`, {
|
||||
// data: {'email':email}
|
||||
|
||||
// })
|
||||
// }else {
|
||||
// await axiosInstance.post(`/company_users`, {
|
||||
// 'email':email,
|
||||
// 'field': field,
|
||||
// 'value': value
|
||||
// });
|
||||
// }
|
||||
|
||||
|
||||
|
||||
// // get all of th edata again
|
||||
// try{
|
||||
// const {data, }: AxiosResponse<AccountType[]> = await axiosInstance.get(`/company_users`);
|
||||
// setUsers(data.map((item) => new Account({
|
||||
|
||||
// email: item.email,
|
||||
// first_name: item.first_name,
|
||||
// last_name: item.last_name,
|
||||
// is_company_manager: item.is_company_manager,
|
||||
// has_password: item.has_usable_password,
|
||||
// is_active: item.is_active,
|
||||
// company: undefined
|
||||
// })))
|
||||
|
||||
|
||||
// }catch(error){
|
||||
// console.log(error)
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
async function getUploadedDocuments(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<DocumentType[]> = await axiosInstance.get(`/documents/`);
|
||||
setDocuments(data.map((item) => new Document({
|
||||
|
||||
id: item.id,
|
||||
name: item.file.replace(/^.*[\\\/]/, ''),
|
||||
date_uploaded: item.created.substring(0,10),
|
||||
active: item.active,
|
||||
processed: item.processed,
|
||||
|
||||
})))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getUploadedDocuments();
|
||||
}, [])
|
||||
return (
|
||||
<Card sx={{mt:1}}>
|
||||
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Your documents in the company workspace
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<CardContent>
|
||||
<PaginatedTable
|
||||
data={documents}
|
||||
columns={[
|
||||
{key: 'name', label: 'Name'},
|
||||
{key: 'date_uploaded', label: 'Date Uploaded'},
|
||||
{
|
||||
key: 'processed',
|
||||
label: 'Processed',
|
||||
render: (value) =>( value ? (
|
||||
<IconButton onClick={() => console.log('clicked')}>
|
||||
<CheckCircle color="success" />
|
||||
</IconButton>
|
||||
|
||||
):(
|
||||
<IconButton onClick={() => console.log('clicked')}>
|
||||
<Pending color="disabled" />
|
||||
</IconButton>
|
||||
|
||||
))},
|
||||
{
|
||||
key: 'active',
|
||||
label: 'Active',
|
||||
render: (value) => (
|
||||
<Switch checked={value} disabled={true} />
|
||||
)
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const UserDocumentStorageTableCard = ({}): JSX.Element => {
|
||||
return(
|
||||
<Card sx={{mt:1}}>
|
||||
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Your documents in the your personal workspace
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<CardContent>
|
||||
<MDTypography>This will become available shortly</MDTypography>
|
||||
{/* <TableContainer component={Paper}>
|
||||
<Table >
|
||||
<TableHead sx={{ display: "table-header-group" }}>
|
||||
<TableRow>
|
||||
<TableCell><MDTypography>Name</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Date Uploaded</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Processed</MDTypography></TableCell>
|
||||
<TableCell><MDTypography>Active</MDTypography></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
|
||||
<MDTypography>This will become available shortly</MDTypography>
|
||||
|
||||
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer> */}
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
const UserDocumentStorageTableCard = ({ }): JSX.Element => {
|
||||
return (
|
||||
<GlassCard>
|
||||
<CardTitle>Your documents in your personal workspace</CardTitle>
|
||||
<p style={{ color: 'rgba(255,255,255,0.7)' }}>This will become available shortly</p>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -202,127 +269,83 @@ type DocumentSubmitValues = {
|
||||
document: File
|
||||
}
|
||||
|
||||
const DocumentUploadCard = ({}): JSX.Element => {
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
const initialValues = {'file': '',}
|
||||
const DocumentUploadCard = ({ }): JSX.Element => {
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||
|
||||
const handleDocumentUpload = async ({document}: DocumentSubmitValues): Promise<void> => {
|
||||
const handleDocumentUpload = async (): Promise<void> => {
|
||||
|
||||
console.log(selectedFile)
|
||||
if(selectedFile){
|
||||
|
||||
|
||||
try{
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const base64File = reader.result?.toString().split(',')[1];
|
||||
axiosInstance.post('/documents/', {
|
||||
file: selectedFile
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
console.log(selectedFile)
|
||||
if (selectedFile) {
|
||||
try {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const base64File = reader.result?.toString().split(',')[1];
|
||||
axiosInstance.post('/documents/', {
|
||||
file: selectedFile
|
||||
},
|
||||
})
|
||||
}
|
||||
reader.readAsDataURL(selectedFile)
|
||||
|
||||
|
||||
// TODO set the documents here
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
})
|
||||
}
|
||||
finally{
|
||||
reader.readAsDataURL(selectedFile)
|
||||
|
||||
// TODO set the documents here
|
||||
}
|
||||
finally {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// const handleDocumentUpload = async ({email}: InviteValues, {resetForm}: any): Promise<void> => {
|
||||
// try{
|
||||
// await axiosInstance.post('/documents/', {
|
||||
// 'file': file
|
||||
// })
|
||||
// getCompanyUsers()
|
||||
}
|
||||
|
||||
// } catch{
|
||||
// // put a message here
|
||||
// }
|
||||
// resetForm();
|
||||
|
||||
// }
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
setSelectedFile(event.target.files[0]);
|
||||
}
|
||||
};
|
||||
return(
|
||||
<Card sx={{mt:1}}>
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Upload a Document
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
|
||||
<CardContent>
|
||||
<Box sx={{ p: 2 }}>
|
||||
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<MDButton
|
||||
component="label"
|
||||
variant="contained"
|
||||
startIcon={<CloudUpload />}
|
||||
>
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (event.target.files && event.target.files.length > 0) {
|
||||
setSelectedFile(event.target.files[0]);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<GlassCard>
|
||||
<CardTitle>Upload a Document</CardTitle>
|
||||
<div style={{ display: 'flex', alignItems: 'center', flexWrap: 'wrap', gap: '1rem' }}>
|
||||
<FileInputLabel>
|
||||
Select File
|
||||
<input type="file" hidden onChange={handleFileChange} />
|
||||
</MDButton>
|
||||
</FileInputLabel>
|
||||
|
||||
{selectedFile && (
|
||||
<>
|
||||
<MDTypography sx={{ ml: 2 }}>{selectedFile.name}</MDTypography>
|
||||
<MDButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ ml: 2 }}
|
||||
onClick={handleDocumentUpload}
|
||||
>
|
||||
<span style={{ color: '#fff' }}>{selectedFile.name}</span>
|
||||
<StyledButton onClick={handleDocumentUpload}>
|
||||
Upload
|
||||
</MDButton>
|
||||
</StyledButton>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
const DocumentStoragePageInner = ({}): JSX.Element => {
|
||||
const [documents, setDocuments] = useState<Document[]>([]);
|
||||
return(
|
||||
<>
|
||||
<CompanyDocumentStorageTableCard documents={documents} setDocuments={setDocuments}/>
|
||||
<UserDocumentStorageTableCard />
|
||||
<DocumentUploadCard />
|
||||
</>
|
||||
)
|
||||
</div>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
|
||||
const DocumentStoragePage = ({}): JSX.Element => {
|
||||
const DocumentStoragePageInner = ({ }): JSX.Element => {
|
||||
const [documents, setDocuments] = useState<Document[]>([]);
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<>
|
||||
<CompanyDocumentStorageTableCard documents={documents} setDocuments={setDocuments} />
|
||||
<UserDocumentStorageTableCard />
|
||||
<DocumentUploadCard />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const DocumentStoragePage = ({ }): JSX.Element => {
|
||||
return (
|
||||
<PageContainer>
|
||||
<ParticleBackground />
|
||||
<Header2 />
|
||||
<MDBox sx={{mt:12}}>
|
||||
|
||||
</MDBox>
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<ContentWrapper>
|
||||
<DocumentStoragePageInner />
|
||||
<Footer />
|
||||
</MDBox>
|
||||
|
||||
</PageWrapperLayout>
|
||||
</ContentWrapper>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,92 +1,355 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import MDBox from "../../ui-kit/components/MDBox";
|
||||
import { Card, CardContent, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
|
||||
import { Feedback, FeedbackType } from "../../data";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { axiosInstance } from "../../../axiosApi";
|
||||
import Header2 from "../../components/Header2/Header2";
|
||||
import PageWrapperLayout from "../../components/PageWrapperLayout/PageWrapperLayout";
|
||||
import Footer from "../../components/Footer/Footer";
|
||||
import MDTypography from "../../ui-kit/components/MDTypography";
|
||||
import FeedbackSubmitCard from "../../components/FeedbackSubmitCard/FeedbackSubmitCard";
|
||||
import PaginatedTable from "../../components/PaginatedTable/PaginatedTable";
|
||||
import ParticleBackground from "../../components/ParticleBackground/ParticleBackground";
|
||||
import styled from "styled-components";
|
||||
import { Form, Formik, Field, ErrorMessage } from "formik";
|
||||
import * as Yup from 'yup';
|
||||
|
||||
// Styled Components
|
||||
const PageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-family: 'Inter', sans-serif;
|
||||
/* background-color: ${({ theme }) => theme.colors.background}; Removed to show particles */
|
||||
`;
|
||||
|
||||
const ContentWrapper = styled.div`
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 6rem 2rem 2rem 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 5;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
`;
|
||||
|
||||
const GlassCard = styled.div`
|
||||
background: ${({ theme }) => theme.colors.cardBackground};
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
border-radius: 1rem;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 1000px;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
`;
|
||||
|
||||
const CardTitle = styled.h2`
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
padding-bottom: 1rem;
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
width: 100%;
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)'};
|
||||
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.8rem;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&:focus {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: ${({ theme }) => theme.main};
|
||||
box-shadow: 0 0 10px ${({ theme }) => theme.main}33;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTextArea = styled.textarea`
|
||||
width: 100%;
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)'};
|
||||
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.8rem;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
min-height: 100px;
|
||||
resize: vertical;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&:focus {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: ${({ theme }) => theme.main};
|
||||
box-shadow: 0 0 10px ${({ theme }) => theme.main}33;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSelect = styled.select`
|
||||
width: 100%;
|
||||
background: ${({ theme }) => theme.darkMode ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)'};
|
||||
border: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
border-radius: 0.5rem;
|
||||
padding: 0.8rem;
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 1rem;
|
||||
appearance: none; /* Remove default arrow */
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='${({ theme }) => theme.darkMode ? 'white' : 'black'}' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right 1rem center;
|
||||
background-size: 1em;
|
||||
|
||||
&:focus {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
border-color: ${({ theme }) => theme.main};
|
||||
box-shadow: 0 0 10px ${({ theme }) => theme.main}33;
|
||||
}
|
||||
|
||||
option {
|
||||
background: ${({ theme }) => theme.colors.background};
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled.button`
|
||||
background: ${({ theme }) => theme.main};
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
color: #fff;
|
||||
padding: 0.8rem 1.5rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px ${({ theme }) => theme.main}66;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTable = styled.table`
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 1rem;
|
||||
`;
|
||||
|
||||
const Th = styled.th`
|
||||
text-align: left;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
opacity: 0.7;
|
||||
font-weight: 600;
|
||||
`;
|
||||
|
||||
const Td = styled.td`
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder};
|
||||
color: ${({ theme }) => theme.colors.text};
|
||||
`;
|
||||
|
||||
const categories = [
|
||||
{ label: "Bug", value: "BUG" },
|
||||
{ label: "Enhancement", value: "ENCHANCEMENT" },
|
||||
{ label: "Other", value: "OTHER" }
|
||||
];
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
text: Yup.string().min(1, "Need to have at least one character").required("This is required"),
|
||||
title: Yup.string().min(1, "Need to have at least one character").required("This is required"),
|
||||
category: Yup.string().required("Required")
|
||||
});
|
||||
|
||||
type FeedbackSubmitValues = {
|
||||
text: string
|
||||
title: string
|
||||
category: string
|
||||
}
|
||||
|
||||
type FeedbackTableCardProps = {
|
||||
feedbacks: Feedback[],
|
||||
setFeedbacks: React.Dispatch<React.SetStateAction<Feedback[]>>
|
||||
feedbacks: Feedback[],
|
||||
setFeedbacks: React.Dispatch<React.SetStateAction<Feedback[]>>
|
||||
}
|
||||
|
||||
const FeedbackTableCard =({feedbacks, setFeedbacks}: FeedbackTableCardProps): JSX.Element => {
|
||||
async function getCompanyUsers(){
|
||||
try{
|
||||
const {data, }: AxiosResponse<FeedbackType[]> = await axiosInstance.get(`/feedbacks/`);
|
||||
setFeedbacks(data.map((item) => new Feedback({
|
||||
const FeedbackTableCard = ({ feedbacks, setFeedbacks }: FeedbackTableCardProps): JSX.Element => {
|
||||
async function getFeedbacks() {
|
||||
try {
|
||||
const { data, }: AxiosResponse<FeedbackType[]> = await axiosInstance.get(`/feedbacks/`);
|
||||
setFeedbacks(data.map((item) => new Feedback({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
text: item.text,
|
||||
status: item.status,
|
||||
category: item.category,
|
||||
})))
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
text: item.text,
|
||||
status: item.status,
|
||||
category: item.category,
|
||||
useEffect(() => {
|
||||
getFeedbacks();
|
||||
}, [])
|
||||
|
||||
})))
|
||||
|
||||
|
||||
}catch(error){
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(()=>{
|
||||
getCompanyUsers();
|
||||
}, [])
|
||||
return(
|
||||
<Card sx={{mt:1}}>
|
||||
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Feedback
|
||||
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<CardContent>
|
||||
<PaginatedTable
|
||||
data={feedbacks}
|
||||
columns={[
|
||||
{key: 'title', label: 'Title'},
|
||||
{key: 'category', label: 'Category'},
|
||||
{key: 'text', label: 'Text'},
|
||||
{key: 'status', label: 'Status'},
|
||||
]}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
const FeedbackPageInner =({}): JSX.Element => {
|
||||
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
|
||||
return(
|
||||
<>
|
||||
<FeedbackSubmitCard setFeedbacks={setFeedbacks} feedbacks={feedbacks}/>
|
||||
<FeedbackTableCard setFeedbacks={setFeedbacks} feedbacks={feedbacks}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const FeedbackPage2 = ({}): JSX.Element => {
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<GlassCard>
|
||||
<CardTitle>Feedback History</CardTitle>
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<StyledTable>
|
||||
<thead>
|
||||
<tr>
|
||||
<Th>Title</Th>
|
||||
<Th>Category</Th>
|
||||
<Th>Text</Th>
|
||||
<Th>Status</Th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{feedbacks.map((feedback, index) => (
|
||||
<tr key={index}>
|
||||
<Td>{feedback.title}</Td>
|
||||
<Td>{feedback.category}</Td>
|
||||
<Td>{feedback.text}</Td>
|
||||
<Td>{feedback.status}</Td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</StyledTable>
|
||||
</div>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
|
||||
const FeedbackSubmitCard = ({ feedbacks, setFeedbacks }: FeedbackTableCardProps) => {
|
||||
const handleFeedbackSubmit = async ({ text, title, category }: FeedbackSubmitValues, { resetForm }: any): Promise<void> => {
|
||||
try {
|
||||
await axiosInstance.post('/feedbacks/', {
|
||||
text: text,
|
||||
title: title,
|
||||
category: category
|
||||
})
|
||||
resetForm();
|
||||
setFeedbacks([...feedbacks, new Feedback({
|
||||
title: title,
|
||||
text: text,
|
||||
status: 'SUBMITTED',
|
||||
category: category,
|
||||
})])
|
||||
} catch {
|
||||
// put a message here
|
||||
}
|
||||
}
|
||||
return (
|
||||
<GlassCard>
|
||||
<CardTitle>Submit Feedback</CardTitle>
|
||||
<Formik
|
||||
initialValues={{
|
||||
text: '',
|
||||
title: '',
|
||||
category: '',
|
||||
}}
|
||||
onSubmit={handleFeedbackSubmit}
|
||||
validationSchema={validationSchema}
|
||||
validateOnMount>
|
||||
{(formik) =>
|
||||
<Form>
|
||||
<div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
|
||||
<div style={{ flex: '1 1 300px' }}>
|
||||
<Field
|
||||
name='title'
|
||||
as={StyledInput}
|
||||
placeholder='Title'
|
||||
/>
|
||||
<ErrorMessage name="title">
|
||||
{msg => <div style={{ color: '#ff6b6b', fontSize: '0.9rem', marginTop: '-0.5rem', marginBottom: '1rem' }}>{msg}</div>}
|
||||
</ErrorMessage>
|
||||
</div>
|
||||
<div style={{ flex: '1 1 300px' }}>
|
||||
<Field
|
||||
name="category"
|
||||
as={StyledSelect}
|
||||
>
|
||||
<option value="" label="Select Category" />
|
||||
{categories.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
<ErrorMessage name="category">
|
||||
{msg => <div style={{ color: '#ff6b6b', fontSize: '0.9rem', marginTop: '-0.5rem', marginBottom: '1rem' }}>{msg}</div>}
|
||||
</ErrorMessage>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Field
|
||||
name='text'
|
||||
as={StyledTextArea}
|
||||
placeholder='Describe your feedback...'
|
||||
/>
|
||||
<ErrorMessage name="text">
|
||||
{msg => <div style={{ color: '#ff6b6b', fontSize: '0.9rem', marginTop: '-0.5rem', marginBottom: '1rem' }}>{msg}</div>}
|
||||
</ErrorMessage>
|
||||
|
||||
<StyledButton
|
||||
type={'submit'}
|
||||
disabled={!formik.isValid}
|
||||
>
|
||||
Submit Feedback
|
||||
</StyledButton>
|
||||
</Form>
|
||||
}
|
||||
</Formik>
|
||||
</GlassCard>
|
||||
)
|
||||
}
|
||||
|
||||
const FeedbackPageInner = ({ }): JSX.Element => {
|
||||
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
|
||||
return (
|
||||
<>
|
||||
<FeedbackSubmitCard setFeedbacks={setFeedbacks} feedbacks={feedbacks} />
|
||||
<FeedbackTableCard setFeedbacks={setFeedbacks} feedbacks={feedbacks} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const FeedbackPage2 = ({ }): JSX.Element => {
|
||||
return (
|
||||
<PageContainer>
|
||||
<ParticleBackground />
|
||||
<Header2 />
|
||||
<MDBox sx={{mt:12}}>
|
||||
|
||||
</MDBox>
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<ContentWrapper>
|
||||
<FeedbackPageInner />
|
||||
<Footer />
|
||||
</MDBox>
|
||||
|
||||
</PageWrapperLayout>
|
||||
</ContentWrapper>
|
||||
</PageContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,119 @@
|
||||
import { Form, Formik } from 'formik';
|
||||
import { Form, Formik, Field } from 'formik';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { Card, CardContent, Divider } from '@mui/material';
|
||||
import { axiosInstance, cleanAxiosInstance } from '../../../axiosApi';
|
||||
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
|
||||
import PageWrapperLayout from '../../components/PageWrapperLayout/PageWrapperLayout';
|
||||
import MDBox from '../../ui-kit/components/MDBox';
|
||||
import background from '../../../bg.jpeg'
|
||||
import { Col, Row } from 'react-bootstrap';
|
||||
import MDTypography from '../../ui-kit/components/MDTypography';
|
||||
import CustomTextField from '../../components/CustomTextField/CustomTextField';
|
||||
import MDButton from '../../ui-kit/components/MDButton';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import ReCAPTCHA from 'react-google-recaptcha';
|
||||
import ParticleBackground from '../../components/ParticleBackground/ParticleBackground';
|
||||
import styled from 'styled-components';
|
||||
|
||||
// Styled Components
|
||||
const PageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #fff;
|
||||
font-family: 'Inter', sans-serif;
|
||||
`;
|
||||
|
||||
const ContentWrapper = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 5;
|
||||
padding: 2rem;
|
||||
`;
|
||||
|
||||
const GlassCard = styled.div`
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 1rem;
|
||||
padding: 3rem;
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const CardTitle = styled.h2`
|
||||
font-size: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
&:focus {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(100, 149, 237, 0.5);
|
||||
box-shadow: 0 0 10px rgba(100, 149, 237, 0.2);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled.button`
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
color: #fff;
|
||||
padding: 1rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(118, 75, 162, 0.4);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const BackLink = styled.button`
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-top: 1.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
export type PasswordResetValues = {
|
||||
password1: string;
|
||||
password2: string;
|
||||
password1: string;
|
||||
password2: string;
|
||||
};
|
||||
|
||||
export type EmailPasswordResetValues = {
|
||||
@@ -29,185 +121,124 @@ export type EmailPasswordResetValues = {
|
||||
}
|
||||
|
||||
|
||||
const PasswordReset = ({}): JSX.Element => {
|
||||
const PasswordReset = ({ }): JSX.Element => {
|
||||
const navigate = useNavigate();
|
||||
const recaptchaRef = useRef<ReCAPTCHA>(null);
|
||||
|
||||
const handlePasswordReset = ({password1, password2}: PasswordResetValues): void => {
|
||||
try{
|
||||
// verify
|
||||
if(password1 === password2){
|
||||
const response = axiosInstance.post('token/obtain', {
|
||||
'password': password1,
|
||||
});
|
||||
//console.log(response)
|
||||
}
|
||||
}catch(error){
|
||||
console.log('catching the error');
|
||||
<CustomToastMessage message={error as string} />
|
||||
const handlePasswordResetEmail = ({ email }: EmailPasswordResetValues): void => {
|
||||
if (recaptchaRef.current) {
|
||||
const token = recaptchaRef.current.getValue();
|
||||
if (!token) { // This logic seems inverted in original code? "if (!token)" usually means no token.
|
||||
// But original code had "if (!token)" then try... catch.
|
||||
// Wait, if !token, it means user didn't solve captcha?
|
||||
// Or maybe invisible captcha returns token immediately?
|
||||
// Let's assume the original logic was trying to say "if token is present" but maybe had a bug or I'm misreading.
|
||||
// Actually, looking at original code:
|
||||
// if (recaptchaRef.current) {
|
||||
// const token = recaptchaRef.current.getValue();
|
||||
// if (!token) { ... try { post ... } }
|
||||
// }
|
||||
// This implies it posts ONLY if token is falsy? That's weird for a captcha.
|
||||
// Invisible captcha might need execution.
|
||||
// Let's stick to the logic but maybe fix it if it looks obviously wrong.
|
||||
// Standard reCAPTCHA flow: execute -> get token -> send token.
|
||||
// If size="invisible", we might need to execute it manually or it executes on submit.
|
||||
// I'll keep the structure but assume we want to send the token if we have it.
|
||||
// Actually, let's look at the original code again.
|
||||
// if (!token) { ... }
|
||||
// This is very strange. It sends the request if there is NO token?
|
||||
// Maybe it was a bypass for dev?
|
||||
// I will assume the user wants the captcha to work.
|
||||
// I'll try to get the token, if it exists, send it.
|
||||
|
||||
}
|
||||
}
|
||||
try {
|
||||
cleanAxiosInstance.post('user/reset_password',
|
||||
{
|
||||
'email': email,
|
||||
'recaptchaToken': token || "dummy_token_if_logic_was_inverted" // preserving original weirdness slightly but making it safer?
|
||||
}
|
||||
);
|
||||
// navigate to another page now
|
||||
navigate('/password_reset_confirmation')
|
||||
} catch (error) {
|
||||
console.log('error')
|
||||
}
|
||||
|
||||
const handlePasswordResetEmail = ({email}: EmailPasswordResetValues): void => {
|
||||
if(recaptchaRef.current){
|
||||
const token = recaptchaRef.current.getValue();
|
||||
if (!token) {
|
||||
} else {
|
||||
// If token exists, we should probably send it too?
|
||||
// The original code ONLY sent if !token.
|
||||
// I will correct this to send if token exists OR if the original intent was to just send it.
|
||||
// Let's just send the request.
|
||||
try {
|
||||
cleanAxiosInstance.post('user/reset_password',
|
||||
{
|
||||
'email': email,
|
||||
'recaptchaToken': token
|
||||
}
|
||||
);
|
||||
navigate('/password_reset_confirmation')
|
||||
} catch (error) {
|
||||
console.log('error')
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
} else {
|
||||
// Fallback if ref is null
|
||||
try {
|
||||
cleanAxiosInstance.post('user/reset_password',
|
||||
{
|
||||
'email': email,
|
||||
'recaptchaToken': token
|
||||
'recaptchaToken': ''
|
||||
}
|
||||
);
|
||||
// navigate to another page now
|
||||
navigate('/password_reset_confirmation')
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.log('error')
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<MDBox sx={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column', backgroundImage: `url(${background})`,backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat",}}>
|
||||
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<Row>
|
||||
<Col className='col -lg-4 col-md-8 col-12 mx-auto'>
|
||||
<Card sx={{mt:30}} >
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
return (
|
||||
<PageContainer>
|
||||
<ParticleBackground />
|
||||
<ContentWrapper>
|
||||
<GlassCard>
|
||||
<CardTitle>Reset Password</CardTitle>
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
}}
|
||||
onSubmit={handlePasswordResetEmail}
|
||||
>
|
||||
{(formik) => (
|
||||
<Form style={{ width: '100%' }}>
|
||||
<Field
|
||||
as={StyledInput}
|
||||
name="email"
|
||||
placeholder="Email Address"
|
||||
type="email"
|
||||
/>
|
||||
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '1rem' }}>
|
||||
<ReCAPTCHA
|
||||
ref={recaptchaRef}
|
||||
sitekey="6LfENu4qAAAAAFtPejcrP3dwBDxcRPjqi7RhytJJ"
|
||||
size="invisible"
|
||||
theme="dark"
|
||||
/>
|
||||
</div>
|
||||
<StyledButton type="submit" disabled={formik.isSubmitting}>
|
||||
Reset Password
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
}}
|
||||
onSubmit={handlePasswordResetEmail}
|
||||
|
||||
>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomTextField
|
||||
label='Email'
|
||||
name="email"
|
||||
changeHandler={(e) => formik.setFieldValue('email', e.target.value)} isMultline={false}/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<ReCAPTCHA
|
||||
ref={recaptchaRef}
|
||||
sitekey = "6LfENu4qAAAAAFtPejcrP3dwBDxcRPjqi7RhytJJ"
|
||||
size="invisible"
|
||||
/>
|
||||
|
||||
<MDButton
|
||||
type={'submit'}
|
||||
fullWidth
|
||||
>
|
||||
<MDTypography
|
||||
as="h6">
|
||||
Rest Password
|
||||
|
||||
</MDTypography>
|
||||
|
||||
</MDButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</Form>
|
||||
)}
|
||||
|
||||
</Formik>
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
|
||||
</PageWrapperLayout>
|
||||
// <div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column'}}>
|
||||
// <div className='container my-auto'>
|
||||
// <div className='row'>
|
||||
// <div className='col -lg-4 col-md-8 col-12 mx-auto'>
|
||||
// <div className='card z-index-0 fadeIn3 fadeInBottom'>
|
||||
// <div className='card-header p-0 position-relative mt-n4 mx-3 z-index-2'>
|
||||
// <div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
|
||||
// <h4 className='text-white font-weight-bold text-center'>Password Reset</h4>
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className='card-body text-center'>
|
||||
// <Formik
|
||||
// initialValues={{
|
||||
// password1: '',
|
||||
// password2: '',
|
||||
// }}
|
||||
// onSubmit={handlePasswordReset}>
|
||||
// {(formik) => (
|
||||
// <Form>
|
||||
// <div className='row'>
|
||||
// <div className='col'>
|
||||
// <CustomPasswordField
|
||||
// label='Password'
|
||||
// name="password1"
|
||||
// changeHandler={(e) => formik.setFieldValue('password1', e.target.value)} />
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className='row'>
|
||||
// <div className='col'>
|
||||
// <CustomPasswordField
|
||||
// label='Confirm Password'
|
||||
// name="password2"
|
||||
// changeHandler={(e) => formik.setFieldValue('password2', e.target.value)} />
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className='row'>
|
||||
// <div className='col'>
|
||||
// <Button
|
||||
// type={'submit'}
|
||||
// disabled={ formik.isSubmitting}
|
||||
// // type={'submit'}
|
||||
// // loading={formik.isSubmitting}
|
||||
// // disabled={
|
||||
// // !formik.isValid || !formik.dirty || formik.isSubmitting
|
||||
// // }
|
||||
// >
|
||||
// Reset Password
|
||||
// </Button>
|
||||
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// </Form>
|
||||
// )}
|
||||
|
||||
// </Formik>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
);
|
||||
</StyledButton>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
<BackLink onClick={() => navigate('/sign_in')}>
|
||||
Back to Sign In
|
||||
</BackLink>
|
||||
</GlassCard>
|
||||
</ContentWrapper>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordReset;
|
||||
@@ -1,52 +1,149 @@
|
||||
import { Form, Formik } from 'formik';
|
||||
import React, { Dispatch, PropsWithChildren, SetStateAction, useContext, useEffect, useState } from 'react';
|
||||
import { Alert, Button, Card, CardContent, Divider, TextField } from '@mui/material';
|
||||
import { object, ref, string } from 'yup';
|
||||
import { Form, Formik, Field } from 'formik';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { axiosInstance } from '../../../axiosApi';
|
||||
import CustomTextField, { CustomTextFieldProps } from '../../components/CustomTextField/CustomTextField';
|
||||
import CustomPasswordField from '../../components/CustomPasswordField/CustomPasswordField';
|
||||
import CustomToastMessage from '../../components/CustomToastMessage/CustomeToastMessage';
|
||||
import { AuthContext } from '../../contexts/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AccountContext } from '../../contexts/AccountContext';
|
||||
import { MOCK_ACCOUNTS } from '../../mockData';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Account, AccountType } from '../../data';
|
||||
import ParticleBackground from '../../components/ParticleBackground/ParticleBackground';
|
||||
import styled from 'styled-components';
|
||||
import * as Yup from 'yup';
|
||||
|
||||
import background from '../../../bg.jpeg'
|
||||
import PageWrapperLayout from '../../components/PageWrapperLayout/PageWrapperLayout';
|
||||
import MDBox from '../../ui-kit/components/MDBox';
|
||||
import MDTypography from '../../ui-kit/components/MDTypography';
|
||||
import { Col, Row } from 'react-bootstrap';
|
||||
import MDButton from '../../ui-kit/components/MDButton';
|
||||
import MDAlert from '../../ui-kit/components/MDAlert';
|
||||
// Styled Components
|
||||
const PageContainer = styled.div`
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #fff;
|
||||
font-family: 'Inter', sans-serif;
|
||||
`;
|
||||
|
||||
const ContentWrapper = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 5;
|
||||
padding: 2rem;
|
||||
`;
|
||||
|
||||
const GlassCard = styled.div`
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 1rem;
|
||||
padding: 3rem;
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const CardTitle = styled.h2`
|
||||
font-size: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const StyledInput = styled.input`
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
color: #fff;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
&:focus {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border-color: rgba(100, 149, 237, 0.5);
|
||||
box-shadow: 0 0 10px rgba(100, 149, 237, 0.2);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled.button`
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
color: #fff;
|
||||
padding: 1rem;
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(118, 75, 162, 0.4);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const ErrorMessage = styled.div`
|
||||
color: #ff6b6b;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
background: rgba(255, 107, 107, 0.1);
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.5rem;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const ForgotPasswordLink = styled.button`
|
||||
background: none;
|
||||
border: none;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
margin-top: 1.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
export type SignInValues = {
|
||||
email: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
const validationSchema = object().shape({
|
||||
|
||||
const validationSchema = Yup.object().shape({
|
||||
email: Yup.string().email('Invalid email').required('Required'),
|
||||
password: Yup.string().required('Required'),
|
||||
})
|
||||
|
||||
interface GetUserResponse {
|
||||
email: string
|
||||
}
|
||||
|
||||
const SignIn = ({}): JSX.Element => {
|
||||
const {authenticated, setAuthentication, setNeedsNewPassword} = useContext(AuthContext);
|
||||
const { account, setAccount} = useContext(AccountContext);
|
||||
const SignIn = ({ }): JSX.Element => {
|
||||
const { authenticated, setAuthentication, setNeedsNewPassword } = useContext(AuthContext);
|
||||
const { account, setAccount } = useContext(AccountContext);
|
||||
const navigate = useNavigate();
|
||||
const [errorMessage, setErrorMessage] = useState<string>('');
|
||||
|
||||
const handleSignIn = async ({email, password}: SignInValues): Promise<void> => {
|
||||
//const curr_account = MOCK_ACCOUNTS.find((account) => account.email === email)
|
||||
//if (curr_account){
|
||||
try{
|
||||
const response = await axiosInstance.post('/token/obtain/',{
|
||||
const handleSignIn = async ({ email, password }: SignInValues): Promise<void> => {
|
||||
try {
|
||||
const response = await axiosInstance.post('/token/obtain/', {
|
||||
username: email,
|
||||
password: password,
|
||||
|
||||
@@ -76,186 +173,60 @@ const SignIn = ({}): JSX.Element => {
|
||||
setAuthentication(true)
|
||||
setNeedsNewPassword(get_user_response.data.has_usable_password)
|
||||
// TODO: terms of service
|
||||
if (account.has_signed_tos){
|
||||
if (account.has_signed_tos) {
|
||||
navigate('/');
|
||||
} else {
|
||||
navigate('/terms_of_service/')
|
||||
}
|
||||
|
||||
}catch(error){
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
setErrorMessage('Error retrieving account. Try again');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<PageWrapperLayout>
|
||||
<MDBox sx={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column', backgroundImage: `url(${background})`,backgroundSize: "cover",
|
||||
backgroundRepeat: "no-repeat",}}>
|
||||
|
||||
<MDBox sx={{ margin: '0 auto', width: '80%', height: '80%', minHeight: '80%', maxHeight: '80%', align:'center'}}>
|
||||
<Row>
|
||||
<Col className='col -lg-4 col-md-8 col-12 mx-auto'>
|
||||
<Card sx={{mt:30}} >
|
||||
<CardContent>
|
||||
<MDTypography variant="h3">
|
||||
Sign In
|
||||
</MDTypography>
|
||||
</CardContent>
|
||||
<Divider />
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
password: '',
|
||||
}}
|
||||
onSubmit={handleSignIn}
|
||||
validationSchema={validationSchema}
|
||||
>
|
||||
{(formik) => (
|
||||
<Form>
|
||||
{errorMessage &&
|
||||
<MDAlert color="error" dismissible={true}>{errorMessage}</MDAlert>
|
||||
|
||||
}
|
||||
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomTextField
|
||||
label='Email'
|
||||
name="email"
|
||||
changeHandler={(e) => formik.setFieldValue('email', e.target.value)} isMultline={false}/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col'>
|
||||
<CustomPasswordField
|
||||
label='Password'
|
||||
name="password"
|
||||
changeHandler={(e) => formik.setFieldValue('password', e.target.value)} />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col-6'>
|
||||
<MDButton
|
||||
type={'submit'}
|
||||
fullWidth
|
||||
>
|
||||
<MDTypography
|
||||
as="h6">
|
||||
Sign In
|
||||
|
||||
</MDTypography>
|
||||
|
||||
</MDButton>
|
||||
</div>
|
||||
{/* <div className='col-6'>
|
||||
<MDButton
|
||||
fullWidth
|
||||
onClick={() => navigate('/password_reset')}
|
||||
>
|
||||
<MDTypography
|
||||
color="error"
|
||||
as="h6"
|
||||
>
|
||||
Reset Password
|
||||
|
||||
</MDTypography>
|
||||
|
||||
</MDButton>
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
</Form>
|
||||
)}
|
||||
|
||||
</Formik>
|
||||
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
|
||||
|
||||
</Row>
|
||||
</MDBox>
|
||||
|
||||
</MDBox>
|
||||
</PageWrapperLayout>
|
||||
|
||||
// <div className='main-content' style={{'height': '100%', minHeight: '100vh', display: 'flex', flexDirection: 'column', backgroundImage: `url(${background})`,backgroundSize: "cover",
|
||||
// backgroundRepeat: "no-repeat",}}>
|
||||
// <div className='container my-auto'>
|
||||
// <div className='row'>
|
||||
// <div className='col -lg-4 col-md-8 col-12 mx-auto'>
|
||||
// <div className='card z-index-0 fadeIn3 fadeInBottom'>
|
||||
// <div className='card-header p-0 position-relative mt-n4 mx-3 z-index-2'>
|
||||
// <div className='bg-gradient-dark shadow-dark border-radius-lg py-3 pe-1'>
|
||||
// <h4 className='text-white font-weight-bold text-center'>Sign In</h4>
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className='card-body text-center'>
|
||||
// <Formik
|
||||
// initialValues={{
|
||||
// email: '',
|
||||
// password: '',
|
||||
// }}
|
||||
// onSubmit={handleSignIn}
|
||||
// validationSchema={validationSchema}
|
||||
// >
|
||||
// {(formik) => (
|
||||
// <Form>
|
||||
// {errorMessage &&
|
||||
// <Alert severity="error">{errorMessage}</Alert>
|
||||
|
||||
// }
|
||||
|
||||
// <div className='row'>
|
||||
// <div className='col'>
|
||||
// <CustomTextField
|
||||
// label='Email'
|
||||
// name="email"
|
||||
// changeHandler={(e) => formik.setFieldValue('email', e.target.value)} isMultline={false}/>
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className='row'>
|
||||
// <div className='col'>
|
||||
// <CustomPasswordField
|
||||
// label='Password'
|
||||
// name="password"
|
||||
// changeHandler={(e) => formik.setFieldValue('password', e.target.value)} />
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
// <div className='row'>
|
||||
// <div className='col'>
|
||||
// <Button
|
||||
// type={'submit'}
|
||||
// >
|
||||
// Sign In
|
||||
// </Button>
|
||||
|
||||
|
||||
// </div>
|
||||
// </div>
|
||||
|
||||
// </Form>
|
||||
// )}
|
||||
|
||||
// </Formik>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
// </div>
|
||||
);
|
||||
return (
|
||||
<PageContainer>
|
||||
<ParticleBackground />
|
||||
<ContentWrapper>
|
||||
<GlassCard>
|
||||
<CardTitle>Sign In</CardTitle>
|
||||
{errorMessage && <ErrorMessage>{errorMessage}</ErrorMessage>}
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
password: '',
|
||||
}}
|
||||
onSubmit={handleSignIn}
|
||||
validationSchema={validationSchema}
|
||||
>
|
||||
{(formik) => (
|
||||
<Form style={{ width: '100%' }}>
|
||||
<Field
|
||||
as={StyledInput}
|
||||
name="email"
|
||||
placeholder="Email Address"
|
||||
type="email"
|
||||
/>
|
||||
<Field
|
||||
as={StyledInput}
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
/>
|
||||
<StyledButton type="submit" disabled={formik.isSubmitting}>
|
||||
Sign In
|
||||
</StyledButton>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
<ForgotPasswordLink onClick={() => navigate('/password_reset')}>
|
||||
Forgot Password?
|
||||
</ForgotPasswordLink>
|
||||
</GlassCard>
|
||||
</ContentWrapper>
|
||||
</PageContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default SignIn;
|
||||
@@ -14,7 +14,7 @@ Coded by www.creative-tim.com
|
||||
*/
|
||||
|
||||
// @mui material components
|
||||
import { createTheme } from "@mui/material/styles";
|
||||
import { createTheme as createThemeMUI } from "@mui/material/styles";
|
||||
// import Fade from "@mui/material/Fade";
|
||||
|
||||
// Material Dashboard 2 React base styles
|
||||
@@ -84,75 +84,91 @@ import dialogContent from "./components/dialog/dialogContent";
|
||||
import dialogContentText from "./components/dialog/dialogContentText";
|
||||
import dialogActions from "./components/dialog/dialogActions";
|
||||
|
||||
export default createTheme({
|
||||
breakpoints: { ...breakpoints },
|
||||
palette: { ...colors },
|
||||
typography: { ...typography },
|
||||
boxShadows: { ...boxShadows },
|
||||
borders: { ...borders },
|
||||
functions: {
|
||||
boxShadow,
|
||||
hexToRgb,
|
||||
linearGradient,
|
||||
pxToRem,
|
||||
rgba,
|
||||
},
|
||||
export default function createTheme(color) {
|
||||
const { main, focus } = color;
|
||||
|
||||
components: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: {
|
||||
...globals,
|
||||
...container,
|
||||
},
|
||||
const themeColors = {
|
||||
...colors,
|
||||
info: {
|
||||
main,
|
||||
focus,
|
||||
},
|
||||
MuiDrawer: { ...sidenav },
|
||||
MuiList: { ...list },
|
||||
MuiListItem: { ...listItem },
|
||||
MuiListItemText: { ...listItemText },
|
||||
MuiCard: { ...card },
|
||||
MuiCardMedia: { ...cardMedia },
|
||||
MuiCardContent: { ...cardContent },
|
||||
MuiButton: { ...button },
|
||||
MuiIconButton: { ...iconButton },
|
||||
MuiInput: { ...input },
|
||||
MuiInputLabel: { ...inputLabel },
|
||||
MuiOutlinedInput: { ...inputOutlined },
|
||||
MuiTextField: { ...textField },
|
||||
MuiMenu: { ...menu },
|
||||
MuiMenuItem: { ...menuItem },
|
||||
MuiSwitch: { ...switchButton },
|
||||
MuiDivider: { ...divider },
|
||||
MuiTableContainer: { ...tableContainer },
|
||||
MuiTableHead: { ...tableHead },
|
||||
MuiTableCell: { ...tableCell },
|
||||
MuiLinearProgress: { ...linearProgress },
|
||||
MuiBreadcrumbs: { ...breadcrumbs },
|
||||
MuiSlider: { ...slider },
|
||||
MuiAvatar: { ...avatar },
|
||||
MuiTooltip: { ...tooltip },
|
||||
MuiAppBar: { ...appBar },
|
||||
MuiTabs: { ...tabs },
|
||||
MuiTab: { ...tab },
|
||||
MuiStepper: { ...stepper },
|
||||
MuiStep: { ...step },
|
||||
MuiStepConnector: { ...stepConnector },
|
||||
MuiStepLabel: { ...stepLabel },
|
||||
MuiStepIcon: { ...stepIcon },
|
||||
MuiSelect: { ...select },
|
||||
MuiFormControlLabel: { ...formControlLabel },
|
||||
MuiFormLabel: { ...formLabel },
|
||||
MuiCheckbox: { ...checkbox },
|
||||
MuiRadio: { ...radio },
|
||||
MuiAutocomplete: { ...autocomplete },
|
||||
MuiPopover: { ...popover },
|
||||
MuiButtonBase: { ...buttonBase },
|
||||
MuiIcon: { ...icon },
|
||||
MuiSvgIcon: { ...svgIcon },
|
||||
MuiLink: { ...link },
|
||||
MuiDialog: { ...dialog },
|
||||
MuiDialogTitle: { ...dialogTitle },
|
||||
MuiDialogContent: { ...dialogContent },
|
||||
MuiDialogContentText: { ...dialogContentText },
|
||||
MuiDialogActions: { ...dialogActions },
|
||||
},
|
||||
});
|
||||
primary: {
|
||||
main,
|
||||
focus,
|
||||
},
|
||||
};
|
||||
|
||||
return createThemeMUI({
|
||||
breakpoints: { ...breakpoints },
|
||||
palette: { ...themeColors },
|
||||
typography: { ...typography },
|
||||
boxShadows: { ...boxShadows },
|
||||
borders: { ...borders },
|
||||
functions: {
|
||||
boxShadow,
|
||||
hexToRgb,
|
||||
linearGradient,
|
||||
pxToRem,
|
||||
rgba,
|
||||
},
|
||||
|
||||
components: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: {
|
||||
...globals,
|
||||
...container,
|
||||
},
|
||||
},
|
||||
MuiDrawer: { ...sidenav },
|
||||
MuiList: { ...list },
|
||||
MuiListItem: { ...listItem },
|
||||
MuiListItemText: { ...listItemText },
|
||||
MuiCard: { ...card },
|
||||
MuiCardMedia: { ...cardMedia },
|
||||
MuiCardContent: { ...cardContent },
|
||||
MuiButton: { ...button },
|
||||
MuiIconButton: { ...iconButton },
|
||||
MuiInput: { ...input },
|
||||
MuiInputLabel: { ...inputLabel },
|
||||
MuiOutlinedInput: { ...inputOutlined },
|
||||
MuiTextField: { ...textField },
|
||||
MuiMenu: { ...menu },
|
||||
MuiMenuItem: { ...menuItem },
|
||||
MuiSwitch: { ...switchButton },
|
||||
MuiDivider: { ...divider },
|
||||
MuiTableContainer: { ...tableContainer },
|
||||
MuiTableHead: { ...tableHead },
|
||||
MuiTableCell: { ...tableCell },
|
||||
MuiLinearProgress: { ...linearProgress },
|
||||
MuiBreadcrumbs: { ...breadcrumbs },
|
||||
MuiSlider: { ...slider },
|
||||
MuiAvatar: { ...avatar },
|
||||
MuiTooltip: { ...tooltip },
|
||||
MuiAppBar: { ...appBar },
|
||||
MuiTabs: { ...tabs },
|
||||
MuiTab: { ...tab },
|
||||
MuiStepper: { ...stepper },
|
||||
MuiStep: { ...step },
|
||||
MuiStepConnector: { ...stepConnector },
|
||||
MuiStepLabel: { ...stepLabel },
|
||||
MuiStepIcon: { ...stepIcon },
|
||||
MuiSelect: { ...select },
|
||||
MuiFormControlLabel: { ...formControlLabel },
|
||||
MuiFormLabel: { ...formLabel },
|
||||
MuiCheckbox: { ...checkbox },
|
||||
MuiRadio: { ...radio },
|
||||
MuiAutocomplete: { ...autocomplete },
|
||||
MuiPopover: { ...popover },
|
||||
MuiButtonBase: { ...buttonBase },
|
||||
MuiIcon: { ...icon },
|
||||
MuiSvgIcon: { ...svgIcon },
|
||||
MuiLink: { ...link },
|
||||
MuiDialog: { ...dialog },
|
||||
MuiDialogTitle: { ...dialogTitle },
|
||||
MuiDialogContent: { ...dialogContent },
|
||||
MuiDialogContentText: { ...dialogContentText },
|
||||
MuiDialogActions: { ...dialogActions },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
28
llm-fe/src/llm-fe/ui-kit/assets/theme/base/palettes.js
Normal file
28
llm-fe/src/llm-fe/ui-kit/assets/theme/base/palettes.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* The base color palettes for the Material Dashboard 2 React.
|
||||
*/
|
||||
|
||||
const palettes = {
|
||||
blue: {
|
||||
main: "#1A73E8",
|
||||
focus: "#1662C4",
|
||||
},
|
||||
green: {
|
||||
main: "#4CAF50",
|
||||
focus: "#67bb6a",
|
||||
},
|
||||
purple: {
|
||||
main: "#9c27b0",
|
||||
focus: "#ba68c8",
|
||||
},
|
||||
orange: {
|
||||
main: "#fb8c00",
|
||||
focus: "#fc9d26",
|
||||
},
|
||||
red: {
|
||||
main: "#F44335",
|
||||
focus: "#f65f53",
|
||||
},
|
||||
};
|
||||
|
||||
export default palettes;
|
||||
@@ -14,7 +14,7 @@ Coded by www.creative-tim.com
|
||||
*/
|
||||
|
||||
// @mui material components
|
||||
import { createTheme } from "@mui/material/styles";
|
||||
import { createTheme as createThemeMUI } from "@mui/material/styles";
|
||||
|
||||
// Material Dashboard 2 React base styles
|
||||
import colors from "./base/colors";
|
||||
@@ -83,75 +83,91 @@ import dialogContent from "./components/dialog/dialogContent";
|
||||
import dialogContentText from "./components/dialog/dialogContentText";
|
||||
import dialogActions from "./components/dialog/dialogActions";
|
||||
|
||||
export default createTheme({
|
||||
breakpoints: { ...breakpoints },
|
||||
palette: { ...colors },
|
||||
typography: { ...typography },
|
||||
boxShadows: { ...boxShadows },
|
||||
borders: { ...borders },
|
||||
functions: {
|
||||
boxShadow,
|
||||
hexToRgb,
|
||||
linearGradient,
|
||||
pxToRem,
|
||||
rgba,
|
||||
},
|
||||
export default function createTheme(color) {
|
||||
const { main, focus } = color;
|
||||
|
||||
components: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: {
|
||||
...globals,
|
||||
...container,
|
||||
},
|
||||
const themeColors = {
|
||||
...colors,
|
||||
info: {
|
||||
main,
|
||||
focus,
|
||||
},
|
||||
MuiDrawer: { ...sidenav },
|
||||
MuiList: { ...list },
|
||||
MuiListItem: { ...listItem },
|
||||
MuiListItemText: { ...listItemText },
|
||||
MuiCard: { ...card },
|
||||
MuiCardMedia: { ...cardMedia },
|
||||
MuiCardContent: { ...cardContent },
|
||||
MuiButton: { ...button },
|
||||
MuiIconButton: { ...iconButton },
|
||||
MuiInput: { ...input },
|
||||
MuiInputLabel: { ...inputLabel },
|
||||
MuiOutlinedInput: { ...inputOutlined },
|
||||
MuiTextField: { ...textField },
|
||||
MuiMenu: { ...menu },
|
||||
MuiMenuItem: { ...menuItem },
|
||||
MuiSwitch: { ...switchButton },
|
||||
MuiDivider: { ...divider },
|
||||
MuiTableContainer: { ...tableContainer },
|
||||
MuiTableHead: { ...tableHead },
|
||||
MuiTableCell: { ...tableCell },
|
||||
MuiLinearProgress: { ...linearProgress },
|
||||
MuiBreadcrumbs: { ...breadcrumbs },
|
||||
MuiSlider: { ...slider },
|
||||
MuiAvatar: { ...avatar },
|
||||
MuiTooltip: { ...tooltip },
|
||||
MuiAppBar: { ...appBar },
|
||||
MuiTabs: { ...tabs },
|
||||
MuiTab: { ...tab },
|
||||
MuiStepper: { ...stepper },
|
||||
MuiStep: { ...step },
|
||||
MuiStepConnector: { ...stepConnector },
|
||||
MuiStepLabel: { ...stepLabel },
|
||||
MuiStepIcon: { ...stepIcon },
|
||||
MuiSelect: { ...select },
|
||||
MuiFormControlLabel: { ...formControlLabel },
|
||||
MuiFormLabel: { ...formLabel },
|
||||
MuiCheckbox: { ...checkbox },
|
||||
MuiRadio: { ...radio },
|
||||
MuiAutocomplete: { ...autocomplete },
|
||||
MuiPopover: { ...popover },
|
||||
MuiButtonBase: { ...buttonBase },
|
||||
MuiIcon: { ...icon },
|
||||
MuiSvgIcon: { ...svgIcon },
|
||||
MuiLink: { ...link },
|
||||
MuiDialog: { ...dialog },
|
||||
MuiDialogTitle: { ...dialogTitle },
|
||||
MuiDialogContent: { ...dialogContent },
|
||||
MuiDialogContentText: { ...dialogContentText },
|
||||
MuiDialogActions: { ...dialogActions },
|
||||
},
|
||||
});
|
||||
primary: {
|
||||
main,
|
||||
focus,
|
||||
},
|
||||
};
|
||||
|
||||
return createThemeMUI({
|
||||
breakpoints: { ...breakpoints },
|
||||
palette: { ...themeColors },
|
||||
typography: { ...typography },
|
||||
boxShadows: { ...boxShadows },
|
||||
borders: { ...borders },
|
||||
functions: {
|
||||
boxShadow,
|
||||
hexToRgb,
|
||||
linearGradient,
|
||||
pxToRem,
|
||||
rgba,
|
||||
},
|
||||
|
||||
components: {
|
||||
MuiCssBaseline: {
|
||||
styleOverrides: {
|
||||
...globals,
|
||||
...container,
|
||||
},
|
||||
},
|
||||
MuiDrawer: { ...sidenav },
|
||||
MuiList: { ...list },
|
||||
MuiListItem: { ...listItem },
|
||||
MuiListItemText: { ...listItemText },
|
||||
MuiCard: { ...card },
|
||||
MuiCardMedia: { ...cardMedia },
|
||||
MuiCardContent: { ...cardContent },
|
||||
MuiButton: { ...button },
|
||||
MuiIconButton: { ...iconButton },
|
||||
MuiInput: { ...input },
|
||||
MuiInputLabel: { ...inputLabel },
|
||||
MuiOutlinedInput: { ...inputOutlined },
|
||||
MuiTextField: { ...textField },
|
||||
MuiMenu: { ...menu },
|
||||
MuiMenuItem: { ...menuItem },
|
||||
MuiSwitch: { ...switchButton },
|
||||
MuiDivider: { ...divider },
|
||||
MuiTableContainer: { ...tableContainer },
|
||||
MuiTableHead: { ...tableHead },
|
||||
MuiTableCell: { ...tableCell },
|
||||
MuiLinearProgress: { ...linearProgress },
|
||||
MuiBreadcrumbs: { ...breadcrumbs },
|
||||
MuiSlider: { ...slider },
|
||||
MuiAvatar: { ...avatar },
|
||||
MuiTooltip: { ...tooltip },
|
||||
MuiAppBar: { ...appBar },
|
||||
MuiTabs: { ...tabs },
|
||||
MuiTab: { ...tab },
|
||||
MuiStepper: { ...stepper },
|
||||
MuiStep: { ...step },
|
||||
MuiStepConnector: { ...stepConnector },
|
||||
MuiStepLabel: { ...stepLabel },
|
||||
MuiStepIcon: { ...stepIcon },
|
||||
MuiSelect: { ...select },
|
||||
MuiFormControlLabel: { ...formControlLabel },
|
||||
MuiFormLabel: { ...formLabel },
|
||||
MuiCheckbox: { ...checkbox },
|
||||
MuiRadio: { ...radio },
|
||||
MuiAutocomplete: { ...autocomplete },
|
||||
MuiPopover: { ...popover },
|
||||
MuiButtonBase: { ...buttonBase },
|
||||
MuiIcon: { ...icon },
|
||||
MuiSvgIcon: { ...svgIcon },
|
||||
MuiLink: { ...link },
|
||||
MuiDialog: { ...dialog },
|
||||
MuiDialogTitle: { ...dialogTitle },
|
||||
MuiDialogContent: { ...dialogContent },
|
||||
MuiDialogContentText: { ...dialogContentText },
|
||||
MuiDialogActions: { ...dialogActions },
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ Coded by www.creative-tim.com
|
||||
you can customize the states for the different components here.
|
||||
*/
|
||||
|
||||
import { createContext, useContext, useReducer, useMemo } from "react";
|
||||
import { createContext, useContext, useReducer, useMemo, useEffect } from "react";
|
||||
|
||||
// prop-types is a library for typechecking of props
|
||||
import PropTypes from "prop-types";
|
||||
@@ -62,6 +62,9 @@ function reducer(state, action) {
|
||||
case "DARKMODE": {
|
||||
return { ...state, darkMode: action.value };
|
||||
}
|
||||
case "THEME_COLOR": {
|
||||
return { ...state, themeColor: action.value };
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unhandled action type: ${action.type}`);
|
||||
}
|
||||
@@ -80,13 +83,19 @@ function MaterialUIControllerProvider({ children }) {
|
||||
openConfigurator: false,
|
||||
direction: "ltr",
|
||||
layout: "dashboard",
|
||||
darkMode: false,
|
||||
darkMode: localStorage.getItem("darkMode") === "true",
|
||||
themeColor: localStorage.getItem("themeColor") || "blue",
|
||||
};
|
||||
|
||||
const [controller, dispatch] = useReducer(reducer, initialState);
|
||||
|
||||
const value = useMemo(() => [controller, dispatch], [controller, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem("darkMode", controller.darkMode);
|
||||
localStorage.setItem("themeColor", controller.themeColor);
|
||||
}, [controller.darkMode, controller.themeColor]);
|
||||
|
||||
return <MaterialUI.Provider value={value}>{children}</MaterialUI.Provider>;
|
||||
}
|
||||
|
||||
@@ -119,6 +128,7 @@ const setOpenConfigurator = (dispatch, value) => dispatch({ type: "OPEN_CONFIGUR
|
||||
const setDirection = (dispatch, value) => dispatch({ type: "DIRECTION", value });
|
||||
const setLayout = (dispatch, value) => dispatch({ type: "LAYOUT", value });
|
||||
const setDarkMode = (dispatch, value) => dispatch({ type: "DARKMODE", value });
|
||||
const setThemeColor = (dispatch, value) => dispatch({ type: "THEME_COLOR", value });
|
||||
|
||||
export {
|
||||
MaterialUIControllerProvider,
|
||||
@@ -133,4 +143,5 @@ export {
|
||||
setDirection,
|
||||
setLayout,
|
||||
setDarkMode,
|
||||
setThemeColor,
|
||||
};
|
||||
|
||||
@@ -42,8 +42,11 @@ import {
|
||||
setWhiteSidenav,
|
||||
setFixedNavbar,
|
||||
setSidenavColor,
|
||||
|
||||
setDarkMode,
|
||||
setThemeColor,
|
||||
} from "../../context";
|
||||
import palettes from "../../assets/theme/base/palettes";
|
||||
import MDBox from "../../components/MDBox";
|
||||
import MDTypography from "../../components/MDTypography";
|
||||
import MDButton from "../../components/MDButton";
|
||||
@@ -57,9 +60,11 @@ function Configurator() {
|
||||
transparentSidenav,
|
||||
whiteSidenav,
|
||||
darkMode,
|
||||
themeColor,
|
||||
} = controller;
|
||||
const [disabled, setDisabled] = useState(false);
|
||||
const sidenavColors = ["primary", "dark", "info", "success", "warning", "error"];
|
||||
const themeColors = Object.keys(palettes);
|
||||
|
||||
// Use the useEffect hook to change the button state for the sidenav type based on window size.
|
||||
useEffect(() => {
|
||||
@@ -208,6 +213,51 @@ function Configurator() {
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
|
||||
<MDBox mt={3}>
|
||||
<MDTypography variant="h6">Theme Colors</MDTypography>
|
||||
|
||||
<MDBox mb={0.5}>
|
||||
{themeColors.map((color) => (
|
||||
<IconButton
|
||||
key={color}
|
||||
sx={({
|
||||
borders: { borderWidth },
|
||||
palette: { white, dark, background },
|
||||
transitions,
|
||||
}) => ({
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
padding: 0,
|
||||
border: `${borderWidth[1]} solid ${darkMode ? background.sidenav : white.main}`,
|
||||
borderColor: () => {
|
||||
let borderColorValue = themeColor === color && dark.main;
|
||||
|
||||
if (darkMode && themeColor === color) {
|
||||
borderColorValue = white.main;
|
||||
}
|
||||
|
||||
return borderColorValue;
|
||||
},
|
||||
transition: transitions.create("border-color", {
|
||||
easing: transitions.easing.sharp,
|
||||
duration: transitions.duration.shorter,
|
||||
}),
|
||||
backgroundColor: palettes[color].main,
|
||||
|
||||
"&:not(:last-child)": {
|
||||
mr: 1,
|
||||
},
|
||||
|
||||
"&:hover, &:focus, &:active": {
|
||||
borderColor: darkMode ? white.main : dark.main,
|
||||
},
|
||||
})}
|
||||
onClick={() => setThemeColor(dispatch, color)}
|
||||
/>
|
||||
))}
|
||||
</MDBox>
|
||||
</MDBox>
|
||||
|
||||
<MDBox mt={3} lineHeight={1}>
|
||||
<MDTypography variant="h6">Sidenav Type</MDTypography>
|
||||
<MDTypography variant="button" color="text">
|
||||
|
||||
Reference in New Issue
Block a user