Files
jambonz-webapp/src/containers/internal/navi/index.tsx
Dave Horton ea2713a021 update deps (#422)
* update deps

* prettier

* wip
2024-04-07 18:42:42 -04:00

263 lines
7.0 KiB
TypeScript

import React, { useEffect, useState, useMemo } from "react";
import { classNames, M, Icon, Button } from "@jambonz/ui-kit";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { Icons, ModalForm } from "src/components";
import { naviTop, naviByo } from "./items";
import { UserMe } from "../user-me";
import {
useSelectState,
useDispatch,
toastSuccess,
toastError,
} from "src/store";
import { getActiveSP, setActiveSP } from "src/store/localStore";
import { postServiceProviders } from "src/api";
import type { NaviItem } from "./items";
import "./styles.scss";
import { ScopedAccess } from "src/components/scoped-access";
import { Scope, UserData } from "src/store/types";
import { USER_ADMIN } from "src/api/constants";
import { ROUTE_LOGIN } from "src/router/routes";
import { Lcr } from "src/api/types";
type CommonProps = {
handleMenu: () => void;
};
type NaviProps = CommonProps & {
mobile: boolean;
className: string;
handleLogout: () => void;
};
type ItemProps = CommonProps & {
item: NaviItem;
user?: UserData;
lcr?: Lcr;
};
const Item = ({ item, user, lcr, handleMenu }: ItemProps) => {
const location = useLocation();
const active = location.pathname.includes(item.route(user));
return (
<li>
<Link
to={item.route(user, lcr)}
className={classNames({ navi__link: true, "txt--jean": true, active })}
onClick={handleMenu}
>
<item.icon />
<span>{item.label}</span>
</Link>
</li>
);
};
export const Navi = ({
mobile,
className,
handleMenu,
handleLogout,
}: NaviProps) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const user = useSelectState("user");
const lcr = useSelectState("lcr");
const accessControl = useSelectState("accessControl");
const serviceProviders = useSelectState("serviceProviders");
const currentServiceProvider = useSelectState("currentServiceProvider");
const [sid, setSid] = useState("");
const [modal, setModal] = useState(false);
const [name, setName] = useState("");
const naviByoFiltered = useMemo(() => {
return naviByo.filter(
(item) => !item.acl || (item.acl && accessControl[item.acl]),
);
}, [accessControl, currentServiceProvider]);
const naviTopFiltered = useMemo(() => {
return naviTop.filter((item) => {
if (item.scope === undefined) {
return true;
} else if (user) {
if (item.restrict) {
return user.access === item.scope;
}
return user.access >= item.scope;
}
});
}, [user]);
const handleSubmit = () => {
postServiceProviders({ name })
.then(({ json }) => {
toastSuccess(
<>
Added new service provider <strong>{name}</strong>
</>,
);
dispatch({ type: "serviceProviders" });
setSid(json.sid);
setActiveSP(json.sid);
setName("");
setModal(false);
})
.catch((error) => {
toastError(error.msg);
});
};
const handleCancel = () => {
setName("");
setModal(false);
};
/** Subscribe to change events on the service provider <select> */
useEffect(() => {
setSid(getActiveSP());
if (sid) {
const serviceProvider = serviceProviders.find(
(sp) => sp.service_provider_sid === sid,
);
if (serviceProvider) {
dispatch({ type: "currentServiceProvider", payload: serviceProvider });
}
}
}, [serviceProviders, sid]);
/** Fetch service providers */
useEffect(() => {
dispatch({ type: "user" });
dispatch({ type: "serviceProviders" });
dispatch({ type: "lcr" });
}, []);
return (
<>
<nav className={className}>
{mobile && (
<div className="navi__top">
<Icon subStyle="white" onClick={handleMenu}>
<Icons.X />
</Icon>
<UserMe />
<Button
small
mainStyle="hollow"
subStyle="white"
onClick={handleLogout}
>
Sign out
</Button>
</div>
)}
<div className="navi__sps">
<div className="smsel smsel--navi">
<div>
<select
value={sid || currentServiceProvider?.service_provider_sid}
onChange={(e) => {
setSid(e.target.value);
setActiveSP(e.target.value);
navigate(ROUTE_LOGIN);
}}
disabled={user?.scope !== USER_ADMIN}
>
{currentServiceProvider ? (
serviceProviders.map((serviceProvider) => {
return (
<option
value={serviceProvider.service_provider_sid}
key={serviceProvider.service_provider_sid}
>
{serviceProvider.name}
</option>
);
})
) : (
<option>&nbsp;</option>
)}
</select>
<span>
<Icons.ChevronUp />
<Icons.ChevronDown />
</span>
</div>
</div>
<ScopedAccess scope={Scope.admin} user={user}>
<button
type="button"
onClick={() => setModal(true)}
title="Add service provider"
className="btnty"
>
<Icons.PlusCircle />
</button>
</ScopedAccess>
</div>
<div className="navi__routes">
<ul>
{naviTopFiltered.map((item) => {
return (
<Item
key={item.label}
user={user}
item={item}
handleMenu={handleMenu}
/>
);
})}
</ul>
</div>
<div className="navi__byo">
<M>BYO Services:</M>
</div>
<div className="navi__routes">
<ul>
{naviByoFiltered.map((item) => (
<Item
key={item.label}
user={user}
lcr={lcr}
item={item}
handleMenu={handleMenu}
/>
))}
</ul>
</div>
<div className="navi__logo">
<img
src="/svg/jambonz--light.svg"
width="128"
height="42"
alt="jambonz"
/>
</div>
</nav>
{modal && (
<ModalForm handleSubmit={handleSubmit} handleCancel={handleCancel}>
<fieldset>
<label htmlFor="name">Add new service provider</label>
<input
id="name"
required
type="text"
name="name"
placeholder="Service provider name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</fieldset>
</ModalForm>
)}
</>
);
};