ENCOA-273

This commit is contained in:
Tiago Ribeiro
2024-12-11 14:09:10 +00:00
parent d074ec390c
commit eabfcd026b
7 changed files with 144 additions and 97 deletions

View File

@@ -1,31 +1,32 @@
import { RolePermission } from "@/resources/entityPermissions"; import { RolePermission } from "@/resources/entityPermissions";
export interface Entity { export interface Entity {
id: string; id: string;
label: string; label: string;
licenses: number
} }
export interface Role { export interface Role {
id: string; id: string;
entityID: string; entityID: string;
permissions: RolePermission[]; permissions: RolePermission[];
label: string; label: string;
isDefault?: boolean isDefault?: boolean
} }
export interface EntityWithRoles extends Entity { export interface EntityWithRoles extends Entity {
roles: Role[]; roles: Role[];
}; };
export type WithLabeledEntities<T> = T extends { entities: { id: string; role: string }[] } export type WithLabeledEntities<T> = T extends { entities: { id: string; role: string }[] }
? Omit<T, "entities"> & { entities: { id: string; label?: string; role: string, roleLabel?: string }[] } ? Omit<T, "entities"> & { entities: { id: string; label?: string; role: string, roleLabel?: string }[] }
: T; : T;
export type WithEntity<T> = T extends { entity?: string } export type WithEntity<T> = T extends { entity?: string }
? Omit<T, "entity"> & { entity: Entity } ? Omit<T, "entity"> & { entity: Entity }
: T; : T;
export type WithEntities<T> = T extends { entities: { id: string; role: string }[] } export type WithEntities<T> = T extends { entities: { id: string; role: string }[] }
? Omit<T, "entities"> & { entities: { entity?: Entity; role?: Role }[] } ? Omit<T, "entities"> & { entities: { entity?: Entity; role?: Role }[] }
: T; : T;

View File

@@ -1,13 +1,14 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type {NextApiRequest, NextApiResponse} from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import {withIronSessionApiRoute} from "iron-session/next"; import { withIronSessionApiRoute } from "iron-session/next";
import {sessionOptions} from "@/lib/session"; import { sessionOptions } from "@/lib/session";
import {deleteEntity, getEntity, getEntityWithRoles} from "@/utils/entities.be"; import { deleteEntity, getEntity, getEntityWithRoles } from "@/utils/entities.be";
import client from "@/lib/mongodb"; import client from "@/lib/mongodb";
import {Entity} from "@/interfaces/entity"; import { Entity } from "@/interfaces/entity";
import { doesEntityAllow } from "@/utils/permissions"; import { doesEntityAllow } from "@/utils/permissions";
import { getUser } from "@/utils/users.be"; import { getUser } from "@/utils/users.be";
import { requestUser } from "@/utils/api"; import { requestUser } from "@/utils/api";
import { isAdmin } from "@/utils/users";
const db = client.db(process.env.MONGODB_DB); const db = client.db(process.env.MONGODB_DB);
@@ -23,7 +24,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
const user = await requestUser(req, res) const user = await requestUser(req, res)
if (!user) return res.status(401).json({ ok: false }); if (!user) return res.status(401).json({ ok: false });
const {id, showRoles} = req.query as {id: string; showRoles: string}; const { id, showRoles } = req.query as { id: string; showRoles: string };
const entity = await (!!showRoles ? getEntityWithRoles : getEntity)(id); const entity = await (!!showRoles ? getEntityWithRoles : getEntity)(id);
res.status(200).json(entity); res.status(200).json(entity);
@@ -39,23 +40,31 @@ async function del(req: NextApiRequest, res: NextApiResponse) {
if (!entity) return res.status(404).json({ ok: false }) if (!entity) return res.status(404).json({ ok: false })
if (!doesEntityAllow(user, entity, "delete_entity") && !["admin", "developer"].includes(user.type)) if (!doesEntityAllow(user, entity, "delete_entity") && !["admin", "developer"].includes(user.type))
return res.status(403).json({ok: false}) return res.status(403).json({ ok: false })
await deleteEntity(entity) await deleteEntity(entity)
return res.status(200).json({ok: true}); return res.status(200).json({ ok: true });
} }
async function patch(req: NextApiRequest, res: NextApiResponse) { async function patch(req: NextApiRequest, res: NextApiResponse) {
const user = await requestUser(req, res) const user = await requestUser(req, res)
if (!user) return res.status(401).json({ ok: false }); if (!user) return res.status(401).json({ ok: false });
const {id} = req.query as {id: string}; const { id } = req.query as { id: string };
if (!user.entities.map((x) => x.id).includes(id)) { if (!user.entities.map((x) => x.id).includes(id) && !isAdmin(user)) {
return res.status(403).json({ok: false}); return res.status(403).json({ ok: false });
} }
const entity = await db.collection<Entity>("entities").updateOne({id}, {$set: {label: req.body.label}}); if (req.body.label) {
const entity = await db.collection<Entity>("entities").updateOne({ id }, { $set: { label: req.body.label } });
return res.status(200).json({ ok: entity.acknowledged });
}
return res.status(200).json({ok: entity.acknowledged}); if (req.body.licenses) {
const entity = await db.collection<Entity>("entities").updateOne({ id }, { $set: { licenses: req.body.licenses } });
return res.status(200).json({ ok: entity.acknowledged });
}
return res.status(200).json({ ok: true });
} }

View File

@@ -1,10 +1,10 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type {NextApiRequest, NextApiResponse} from "next"; import type { NextApiRequest, NextApiResponse } from "next";
import {withIronSessionApiRoute} from "iron-session/next"; import { withIronSessionApiRoute } from "iron-session/next";
import {sessionOptions} from "@/lib/session"; import { sessionOptions } from "@/lib/session";
import {addUsersToEntity, addUserToEntity, createEntity, getEntities, getEntitiesWithRoles} from "@/utils/entities.be"; import { addUsersToEntity, addUserToEntity, createEntity, getEntities, getEntitiesWithRoles } from "@/utils/entities.be";
import {Entity} from "@/interfaces/entity"; import { Entity } from "@/interfaces/entity";
import {v4} from "uuid"; import { v4 } from "uuid";
import { requestUser } from "@/utils/api"; import { requestUser } from "@/utils/api";
export default withIronSessionApiRoute(handler, sessionOptions); export default withIronSessionApiRoute(handler, sessionOptions);
@@ -18,7 +18,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
const user = await requestUser(req, res) const user = await requestUser(req, res)
if (!user) return res.status(401).json({ ok: false }); if (!user) return res.status(401).json({ ok: false });
const {showRoles} = req.query as {showRoles: string}; const { showRoles } = req.query as { showRoles: string };
const getFn = showRoles ? getEntitiesWithRoles : getEntities; const getFn = showRoles ? getEntitiesWithRoles : getEntities;
@@ -31,12 +31,13 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
if (!user) return res.status(401).json({ ok: false }); if (!user) return res.status(401).json({ ok: false });
if (!["admin", "developer"].includes(user.type)) { if (!["admin", "developer"].includes(user.type)) {
return res.status(403).json({ok: false}); return res.status(403).json({ ok: false });
} }
const entity: Entity = { const entity: Entity = {
id: v4(), id: v4(),
label: req.body.label, label: req.body.label,
licenses: req.body.licenses
}; };
const members = req.body.members as string[] | undefined || [] const members = req.body.members as string[] | undefined || []

View File

@@ -1,6 +1,6 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import client from "@/lib/mongodb"; import client from "@/lib/mongodb";
import type {NextApiRequest, NextApiResponse} from "next"; import type { NextApiRequest, NextApiResponse } from "next";
const db = client.db(process.env.MONGODB_DB); const db = client.db(process.env.MONGODB_DB);
@@ -9,8 +9,5 @@ type Data = {
}; };
export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
// await db.collection("users").updateMany({}, {$set: {entities: []}}); res.status(200).json({ name: "John Doe" });
await db.collection("invites").deleteMany({});
res.status(200).json({name: "John Doe"});
} }

View File

@@ -4,34 +4,35 @@ import Layout from "@/components/High/Layout";
import Select from "@/components/Low/Select"; import Select from "@/components/Low/Select";
import Tooltip from "@/components/Low/Tooltip"; import Tooltip from "@/components/Low/Tooltip";
import { useEntityPermission } from "@/hooks/useEntityPermissions"; import { useEntityPermission } from "@/hooks/useEntityPermissions";
import {useListSearch} from "@/hooks/useListSearch"; import { useListSearch } from "@/hooks/useListSearch";
import usePagination from "@/hooks/usePagination"; import usePagination from "@/hooks/usePagination";
import {Entity, EntityWithRoles, Role} from "@/interfaces/entity"; import { Entity, EntityWithRoles, Role } from "@/interfaces/entity";
import {GroupWithUsers, User} from "@/interfaces/user"; import { GroupWithUsers, User } from "@/interfaces/user";
import {sessionOptions} from "@/lib/session"; import { sessionOptions } from "@/lib/session";
import {USER_TYPE_LABELS} from "@/resources/user"; import { USER_TYPE_LABELS } from "@/resources/user";
import { findBy, mapBy, redirect, serialize } from "@/utils"; import { findBy, mapBy, redirect, serialize } from "@/utils";
import {getEntityWithRoles} from "@/utils/entities.be"; import { getEntityWithRoles } from "@/utils/entities.be";
import {convertToUsers, getGroup} from "@/utils/groups.be"; import { convertToUsers, getGroup } from "@/utils/groups.be";
import {shouldRedirectHome} from "@/utils/navigation.disabled"; import { shouldRedirectHome } from "@/utils/navigation.disabled";
import {checkAccess, doesEntityAllow, getTypesOfUser} from "@/utils/permissions"; import { checkAccess, doesEntityAllow, getTypesOfUser } from "@/utils/permissions";
import {getUserName, isAdmin} from "@/utils/users"; import { getUserName, isAdmin } from "@/utils/users";
import {filterAllowedUsers, getEntitiesUsers, getEntityUsers, getLinkedUsers, getSpecificUsers, getUsers} from "@/utils/users.be"; import { filterAllowedUsers, getEntitiesUsers, getEntityUsers, getLinkedUsers, getSpecificUsers, getUsers } from "@/utils/users.be";
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react"; import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react";
import axios from "axios"; import axios from "axios";
import clsx from "clsx"; import clsx from "clsx";
import {withIronSessionSsr} from "iron-session/next"; import { withIronSessionSsr } from "iron-session/next";
import moment from "moment"; import moment from "moment";
import Head from "next/head"; import Head from "next/head";
import Link from "next/link"; import Link from "next/link";
import {useRouter} from "next/router"; import { useRouter } from "next/router";
import {Divider} from "primereact/divider"; import { Divider } from "primereact/divider";
import {useEffect, useMemo, useState} from "react"; import { useEffect, useMemo, useState } from "react";
import { import {
BsChevronLeft, BsChevronLeft,
BsClockFill, BsClockFill,
BsEnvelopeFill, BsEnvelopeFill,
BsFillPersonVcardFill, BsFillPersonVcardFill,
BsHash,
BsPerson, BsPerson,
BsPlus, BsPlus,
BsSquare, BsSquare,
@@ -40,15 +41,15 @@ import {
BsTrash, BsTrash,
BsX, BsX,
} from "react-icons/bs"; } from "react-icons/bs";
import {toast} from "react-toastify"; import { toast } from "react-toastify";
export const getServerSideProps = withIronSessionSsr(async ({req, params}) => { export const getServerSideProps = withIronSessionSsr(async ({ req, params }) => {
const user = req.session.user as User; const user = req.session.user as User;
if (!user) return redirect("/login") if (!user) return redirect("/login")
if (shouldRedirectHome(user)) return redirect("/") if (shouldRedirectHome(user)) return redirect("/")
const {id} = params as {id: string}; const { id } = params as { id: string };
const entity = await getEntityWithRoles(id); const entity = await getEntityWithRoles(id);
if (!entity) return redirect("/entities") if (!entity) return redirect("/entities")
@@ -56,12 +57,12 @@ export const getServerSideProps = withIronSessionSsr(async ({req, params}) => {
if (!doesEntityAllow(user, entity, "view_entities")) return redirect(`/entities`) if (!doesEntityAllow(user, entity, "view_entities")) return redirect(`/entities`)
const linkedUsers = await (isAdmin(user) ? getUsers() : getEntitiesUsers(mapBy(user.entities, 'id'), const linkedUsers = await (isAdmin(user) ? getUsers() : getEntitiesUsers(mapBy(user.entities, 'id'),
{$and: [{type: {$ne: "developer"}}, {type: {$ne: "admin"}}]})) { $and: [{ type: { $ne: "developer" } }, { type: { $ne: "admin" } }] }))
const entityUsers = await (isAdmin(user) ? getEntityUsers(id) : filterAllowedUsers(user, [entity])); const entityUsers = await (isAdmin(user) ? getEntityUsers(id) : filterAllowedUsers(user, [entity]));
const usersWithRole = entityUsers.map((u) => { const usersWithRole = entityUsers.map((u) => {
const e = u.entities.find((e) => e.id === id); const e = u.entities.find((e) => e.id === id);
return {...u, role: findBy(entity.roles, 'id', e?.role)}; return { ...u, role: findBy(entity.roles, 'id', e?.role) };
}); });
return { return {
@@ -74,7 +75,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, params}) => {
}; };
}, sessionOptions); }, sessionOptions);
type UserWithRole = User & {role?: Role}; type UserWithRole = User & { role?: Role };
interface Props { interface Props {
user: User; user: User;
@@ -83,7 +84,7 @@ interface Props {
linkedUsers: User[]; linkedUsers: User[];
} }
export default function Home({user, entity, users, linkedUsers}: Props) { export default function Home({ user, entity, users, linkedUsers }: Props) {
const [isAdding, setIsAdding] = useState(false); const [isAdding, setIsAdding] = useState(false);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [selectedUsers, setSelectedUsers] = useState<string[]>([]); const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
@@ -110,7 +111,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
setIsLoading(true); setIsLoading(true);
axios axios
.patch(`/api/entities/${entity.id}/users`, {add: false, members: selectedUsers}) .patch(`/api/entities/${entity.id}/users`, { add: false, members: selectedUsers })
.then(() => { .then(() => {
toast.success("The entity has been updated successfully!"); toast.success("The entity has been updated successfully!");
router.replace(router.asPath); router.replace(router.asPath);
@@ -132,7 +133,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
const defaultRole = findBy(entity.roles, 'isDefault', true)! const defaultRole = findBy(entity.roles, 'isDefault', true)!
axios axios
.patch(`/api/entities/${entity.id}/users`, {add: true, members: selectedUsers, role: defaultRole.id}) .patch(`/api/entities/${entity.id}/users`, { add: true, members: selectedUsers, role: defaultRole.id })
.then(() => { .then(() => {
toast.success("The entity has been updated successfully!"); toast.success("The entity has been updated successfully!");
router.replace(router.asPath); router.replace(router.asPath);
@@ -153,7 +154,28 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
setIsLoading(true); setIsLoading(true);
axios axios
.patch(`/api/entities/${entity.id}`, {label}) .patch(`/api/entities/${entity.id}`, { label })
.then(() => {
toast.success("The entity has been updated successfully!");
router.replace(router.asPath);
})
.catch((e) => {
console.error(e);
toast.error("Something went wrong!");
})
.finally(() => setIsLoading(false));
};
const editLicenses = () => {
if (!isAdmin(user)) return;
const licenses = prompt("Update the number of licenses:", (entity.licenses || 0).toString());
if (!licenses) return;
if (!parseInt(licenses) || parseInt(licenses) <= 0) return toast.error("Write a valid number of licenses!")
setIsLoading(true);
axios
.patch(`/api/entities/${entity.id}`, { licenses })
.then(() => { .then(() => {
toast.success("The entity has been updated successfully!"); toast.success("The entity has been updated successfully!");
router.replace(router.asPath); router.replace(router.asPath);
@@ -190,7 +212,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
setIsLoading(true); setIsLoading(true);
axios axios
.post(`/api/roles/${role}/users`, {users: selectedUsers}) .post(`/api/roles/${role}/users`, { users: selectedUsers })
.then(() => { .then(() => {
toast.success("The role has been assigned successfully!"); toast.success("The role has been assigned successfully!");
router.replace(router.asPath); router.replace(router.asPath);
@@ -274,7 +296,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl"> className="text-mti-purple hover:text-mti-purple-dark transition ease-in-out duration-300 text-xl">
<BsChevronLeft /> <BsChevronLeft />
</Link> </Link>
<h2 className="font-bold text-2xl">{entity.label}</h2> <h2 className="font-bold text-2xl">{entity.label} {isAdmin(user) && `- ${entity.licenses || 0} licenses`}</h2>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@@ -285,6 +307,15 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
<BsTag /> <BsTag />
<span className="text-xs">Rename Entity</span> <span className="text-xs">Rename Entity</span>
</button> </button>
{isAdmin(user) && (
<button
onClick={editLicenses}
disabled={isLoading || !isAdmin(user)}
className="flex items-center gap-1 px-2 py-2 border rounded-full hover:bg-neutral-100 disabled:hover:bg-transparent disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300">
<BsHash />
<span className="text-xs">Edit Licenses</span>
</button>
)}
<button <button
onClick={() => router.push(`/entities/${entity.id}/roles`)} onClick={() => router.push(`/entities/${entity.id}/roles`)}
disabled={isLoading || !canViewRoles} disabled={isLoading || !canViewRoles}
@@ -325,7 +356,7 @@ export default function Home({user, entity, users, linkedUsers}: Props) {
{entity.roles.map((role) => ( {entity.roles.map((role) => (
<MenuItem key={role.id}> <MenuItem key={role.id}>
<button onClick={() => assignUsersToRole(role.id)} className="p-4 hover:bg-neutral-100 w-32"> <button onClick={() => assignUsersToRole(role.id)} className="p-4 hover:bg-neutral-100 w-32">
{ role.label } {role.label}
</button> </button>
</MenuItem> </MenuItem>
))} ))}

View File

@@ -3,32 +3,32 @@ import Layout from "@/components/High/Layout";
import Input from "@/components/Low/Input"; import Input from "@/components/Low/Input";
import Select from "@/components/Low/Select"; import Select from "@/components/Low/Select";
import Tooltip from "@/components/Low/Tooltip"; import Tooltip from "@/components/Low/Tooltip";
import {useListSearch} from "@/hooks/useListSearch"; import { useListSearch } from "@/hooks/useListSearch";
import usePagination from "@/hooks/usePagination"; import usePagination from "@/hooks/usePagination";
import {Entity, EntityWithRoles} from "@/interfaces/entity"; import { Entity, EntityWithRoles } from "@/interfaces/entity";
import {User} from "@/interfaces/user"; import { User } from "@/interfaces/user";
import {sessionOptions} from "@/lib/session"; import { sessionOptions } from "@/lib/session";
import {USER_TYPE_LABELS} from "@/resources/user"; import { USER_TYPE_LABELS } from "@/resources/user";
import {mapBy, redirect, serialize} from "@/utils"; import { mapBy, redirect, serialize } from "@/utils";
import {getEntities, getEntitiesWithRoles} from "@/utils/entities.be"; import { getEntities, getEntitiesWithRoles } from "@/utils/entities.be";
import {shouldRedirectHome} from "@/utils/navigation.disabled"; import { shouldRedirectHome } from "@/utils/navigation.disabled";
import {getUserName} from "@/utils/users"; import { getUserName } from "@/utils/users";
import {getLinkedUsers, getUsers} from "@/utils/users.be"; import { getLinkedUsers, getUsers } from "@/utils/users.be";
import axios from "axios"; import axios from "axios";
import clsx from "clsx"; import clsx from "clsx";
import {withIronSessionSsr} from "iron-session/next"; import { withIronSessionSsr } from "iron-session/next";
import moment from "moment"; import moment from "moment";
import Head from "next/head"; import Head from "next/head";
import Link from "next/link"; import Link from "next/link";
import {useRouter} from "next/router"; import { useRouter } from "next/router";
import {Divider} from "primereact/divider"; import { Divider } from "primereact/divider";
import {useState} from "react"; import { useState } from "react";
import {BsCheck, BsChevronLeft, BsClockFill, BsEnvelopeFill, BsStopwatchFill} from "react-icons/bs"; import { BsCheck, BsChevronLeft, BsClockFill, BsEnvelopeFill, BsStopwatchFill } from "react-icons/bs";
import {toast, ToastContainer} from "react-toastify"; import { toast, ToastContainer } from "react-toastify";
import { requestUser } from "@/utils/api"; import { requestUser } from "@/utils/api";
import { findAllowedEntities } from "@/utils/permissions"; import { findAllowedEntities } from "@/utils/permissions";
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
const user = await requestUser(req, res) const user = await requestUser(req, res)
if (!user) return redirect("/login") if (!user) return redirect("/login")
@@ -38,7 +38,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const users = await getUsers() const users = await getUsers()
return { return {
props: serialize({user, users: users.filter((x) => x.id !== user.id)}), props: serialize({ user, users: users.filter((x) => x.id !== user.id) }),
}; };
}, sessionOptions); }, sessionOptions);
@@ -47,13 +47,14 @@ interface Props {
users: User[]; users: User[];
} }
export default function Home({user, users}: Props) { export default function Home({ user, users }: Props) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [selectedUsers, setSelectedUsers] = useState<string[]>([]); const [selectedUsers, setSelectedUsers] = useState<string[]>([]);
const [label, setLabel] = useState(""); const [label, setLabel] = useState("");
const [licenses, setLicenses] = useState(0);
const {rows, renderSearch} = useListSearch<User>([["name"], ["corporateInformation", "companyInformation", "name"]], users); const { rows, renderSearch } = useListSearch<User>([["name"], ["corporateInformation", "companyInformation", "name"]], users);
const {items, renderMinimal} = usePagination<User>(rows, 16); const { items, renderMinimal } = usePagination<User>(rows, 16);
const router = useRouter(); const router = useRouter();
@@ -64,7 +65,7 @@ export default function Home({user, users}: Props) {
setIsLoading(true); setIsLoading(true);
axios axios
.post<Entity>(`/api/entities`, {label, members: selectedUsers}) .post<Entity>(`/api/entities`, { label, licenses, members: selectedUsers })
.then((result) => { .then((result) => {
toast.success("Your entity has been created successfully!"); toast.success("Your entity has been created successfully!");
router.replace(`/entities/${result.data.id}`); router.replace(`/entities/${result.data.id}`);
@@ -104,7 +105,7 @@ export default function Home({user, users}: Props) {
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<button <button
onClick={createGroup} onClick={createGroup}
disabled={!label.trim() || isLoading} disabled={!label.trim() || licenses <= 0 || isLoading}
className="flex items-center gap-1 px-2 py-2 border rounded-full border-mti-green bg-mti-green-light text-white hover:bg-mti-green-dark disabled:hover:bg-mti-green-light disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300"> className="flex items-center gap-1 px-2 py-2 border rounded-full border-mti-green bg-mti-green-light text-white hover:bg-mti-green-dark disabled:hover:bg-mti-green-light disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition ease-in-out duration-300">
<BsCheck /> <BsCheck />
<span className="text-xs">Create Entity</span> <span className="text-xs">Create Entity</span>
@@ -112,9 +113,16 @@ export default function Home({user, users}: Props) {
</div> </div>
</div> </div>
<Divider /> <Divider />
<div className="flex flex-col gap-4 w-full"> <div className="w-full grid grid-cols-2 gap-4">
<span className="font-semibold text-xl">Entity Label:</span> <div className="flex flex-col gap-4 w-full">
<Input name="name" onChange={setLabel} type="text" placeholder="Entity A" /> <span className="font-semibold text-xl">Entity Label:</span>
<Input name="name" onChange={setLabel} type="text" placeholder="Entity A" />
</div>
<div className="flex flex-col gap-4 w-full">
<span className="font-semibold text-xl">Licenses:</span>
<Input name="licenses" min={0} onChange={(v) => setLicenses(parseInt(v))} type="number" placeholder="12" />
</div>
</div> </div>
<Divider /> <Divider />
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-4">

View File

@@ -6,7 +6,7 @@ import { ToastContainer } from "react-toastify";
import Layout from "@/components/High/Layout"; import Layout from "@/components/High/Layout";
import { GroupWithUsers, User } from "@/interfaces/user"; import { GroupWithUsers, User } from "@/interfaces/user";
import { shouldRedirectHome } from "@/utils/navigation.disabled"; import { shouldRedirectHome } from "@/utils/navigation.disabled";
import { getUserName } from "@/utils/users"; import { getUserName, isAdmin } from "@/utils/users";
import { convertToUsers, getGroupsForUser } from "@/utils/groups.be"; import { convertToUsers, getGroupsForUser } from "@/utils/groups.be";
import { countEntityUsers, getEntityUsers, getSpecificUsers, getUsers } from "@/utils/users.be"; import { countEntityUsers, getEntityUsers, getSpecificUsers, getUsers } from "@/utils/users.be";
import { checkAccess, findAllowedEntities, getTypesOfUser } from "@/utils/permissions"; import { checkAccess, findAllowedEntities, getTypesOfUser } from "@/utils/permissions";
@@ -64,7 +64,7 @@ export default function Home({ user, entities }: Props) {
</span> </span>
<span className="flex items-center gap-1"> <span className="flex items-center gap-1">
<span className="bg-mti-purple text-white font-semibold px-2">Members</span> <span className="bg-mti-purple text-white font-semibold px-2">Members</span>
<span className="bg-mti-purple-light/50 px-2">{count}</span> <span className="bg-mti-purple-light/50 px-2">{count}{isAdmin(user) && ` / ${entity.licenses || 0}`}</span>
</span> </span>
<span> <span>
{users.map(getUserName).join(", ")}{' '} {users.map(getUserName).join(", ")}{' '}