Some checks failed
Build and Deploy React Package / build-and-publish (push) Failing after 2m49s
211 lines
5.8 KiB
TypeScript
Executable File
211 lines
5.8 KiB
TypeScript
Executable File
import {
|
||
Avatar,
|
||
Divider,
|
||
Modal,
|
||
Stack,
|
||
Text,
|
||
Title,
|
||
UnstyledButton,
|
||
useMantineTheme,
|
||
} from "@mantine/core";
|
||
import "./Sidebar.css";
|
||
import {
|
||
IconDevicesPc,
|
||
IconFileDescription,
|
||
IconFlaskFilled,
|
||
IconLogout,
|
||
IconSettings2,
|
||
IconUsers,
|
||
type Icon,
|
||
type IconProps,
|
||
} from "@tabler/icons-react";
|
||
import {
|
||
Link,
|
||
useLocation,
|
||
useNavigate,
|
||
type NavigateFunction,
|
||
} from "react-router";
|
||
import { useEffect, useState, type ForwardRefExoticComponent } from "react";
|
||
import { routes } from "Routes/Routes";
|
||
import keycloak from "Api/Keycloak/Keycloak";
|
||
import { useAuthenticationStore } from "Stores/AuthenticationStore";
|
||
import CustomButton from "Components/CustomButton/CustomButton";
|
||
|
||
interface SubtleLinkButtonProps {
|
||
link: string;
|
||
Icon: ForwardRefExoticComponent<IconProps & React.RefAttributes<Icon>>;
|
||
text: string;
|
||
color: string;
|
||
selected?: boolean;
|
||
nav: NavigateFunction;
|
||
}
|
||
|
||
function SubtleLinkButton(props: SubtleLinkButtonProps) {
|
||
return (
|
||
<Link to={props.link} className="invisible_link">
|
||
<CustomButton
|
||
icon={<props.Icon color={props.color} size={20} />}
|
||
style="subtle"
|
||
color={props.selected ? props.color : "contrast"}
|
||
text={props.text}
|
||
textAlign="left"
|
||
onClick={() => {
|
||
props.nav(props.link);
|
||
}}
|
||
/>
|
||
</Link>
|
||
);
|
||
}
|
||
|
||
function Sidebar() {
|
||
const { profile, set_profile_picture_path, profile_picture_path } =
|
||
useAuthenticationStore();
|
||
const [open, set_open] = useState(false);
|
||
const theme = useMantineTheme();
|
||
const location = useLocation(); // get current URL
|
||
const navigate = useNavigate();
|
||
|
||
useEffect(() => {
|
||
if (!profile) return;
|
||
|
||
const fetchAvatar = async () => {
|
||
try {
|
||
const response = await fetch(
|
||
`${import.meta.env.VITE_QUANTUM_BACKEND_URL}/user/serve/${profile.id}`,
|
||
{
|
||
headers: {
|
||
Authorization: `Bearer ${keycloak.token}`,
|
||
},
|
||
},
|
||
);
|
||
|
||
if (response.ok) {
|
||
const blob = await response.blob();
|
||
const url = URL.createObjectURL(blob);
|
||
set_profile_picture_path(url);
|
||
}
|
||
} catch (error) {
|
||
console.error("Failed to load avatar:", error);
|
||
}
|
||
};
|
||
|
||
fetchAvatar();
|
||
}, [profile]);
|
||
|
||
return (
|
||
<div className="sidebar">
|
||
<Modal
|
||
opened={open}
|
||
onClose={() => set_open(false)}
|
||
title=<Title size="lg">Выйти?</Title>
|
||
centered
|
||
withCloseButton={false}
|
||
size="auto"
|
||
>
|
||
<div className="sidebar_bottom_bottom">
|
||
<CustomButton
|
||
text="Подтвердить"
|
||
onClick={() =>
|
||
keycloak.logout({
|
||
redirectUri: window.location.origin,
|
||
})
|
||
}
|
||
textSize="lg"
|
||
color="contrast"
|
||
></CustomButton>
|
||
<CustomButton
|
||
onClick={() => set_open(false)}
|
||
text="Отменить"
|
||
textSize="lg"
|
||
style="outline"
|
||
color="contrast"
|
||
></CustomButton>
|
||
</div>
|
||
</Modal>
|
||
|
||
<div className="sidebar_top">
|
||
<SubtleLinkButton
|
||
link={routes.ExperimentsPage.path}
|
||
Icon={IconFlaskFilled}
|
||
text="Эксперименты"
|
||
color={theme.colors.teal[7]}
|
||
selected={location.pathname.startsWith(routes.ExperimentsPage.path)}
|
||
nav={navigate}
|
||
/>
|
||
<SubtleLinkButton
|
||
link={routes.MachinesPage.path}
|
||
Icon={IconDevicesPc}
|
||
text="Вычислительные системы"
|
||
color={theme.colors.violet[7]}
|
||
selected={location.pathname.startsWith(routes.MachinesPage.path)}
|
||
nav={navigate}
|
||
/>
|
||
<SubtleLinkButton
|
||
link={routes.TeamsPage.path}
|
||
Icon={IconUsers}
|
||
text="Команды"
|
||
color={theme.colors.grape[7]}
|
||
selected={location.pathname.startsWith(routes.TeamsPage.path)}
|
||
nav={navigate}
|
||
/>
|
||
<SubtleLinkButton
|
||
link={routes.DocumentationPage.path}
|
||
Icon={IconFileDescription}
|
||
text="Документация"
|
||
color={theme.colors.blue[7]}
|
||
selected={location.pathname.startsWith(routes.DocumentationPage.path)}
|
||
nav={navigate}
|
||
/>
|
||
</div>
|
||
<div className="sidebar_bottom">
|
||
{keycloak.authenticated && (
|
||
<>
|
||
<div className="sidebar_bottom_top">
|
||
<Avatar radius="xl" src={profile_picture_path} />
|
||
<Stack className="userInfo">
|
||
<Text size="md">{profile?.username}</Text>
|
||
<Text size="sm" c="dimmed" truncate="end">
|
||
{profile?.email}
|
||
</Text>
|
||
</Stack>
|
||
</div>
|
||
<Divider
|
||
color="secondaryContrast"
|
||
style={{ width: "100%", height: "5px", margin: "5px" }}
|
||
/>
|
||
<div className="sidebar_bottom_bottom">
|
||
<CustomButton
|
||
style="subtle"
|
||
icon={<IconSettings2 size={26} />}
|
||
text="Настройки"
|
||
color="contrast"
|
||
textSize="lg"
|
||
onClick={() => {
|
||
navigate(routes.SettingsPage.path);
|
||
}}
|
||
/>
|
||
<CustomButton
|
||
style="subtle"
|
||
icon={<IconLogout size={26} />}
|
||
text="Выйти"
|
||
textSize="lg"
|
||
color="contrast"
|
||
onClick={() => {
|
||
set_open(true);
|
||
}}
|
||
/>
|
||
</div>
|
||
</>
|
||
)}
|
||
{!keycloak.authenticated && (
|
||
<UnstyledButton onClick={() => keycloak.login()}>
|
||
Войти / Зарегестрироваться
|
||
</UnstyledButton>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default Sidebar;
|