mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
implemented permissions
This commit is contained in:
parent
42fe4fae4e
commit
0138f20b97
@ -3,7 +3,7 @@ import NavBar from './NavBar'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { ToastContainer } from 'react-toastify'
|
||||
|
||||
export function AppShell({ appName, useAuth, children }) {
|
||||
export function AppShell({ appName, children }) {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
<ToastContainer position="top-right"
|
||||
@ -16,7 +16,7 @@ export function AppShell({ appName, useAuth, children }) {
|
||||
draggable
|
||||
pauseOnHover
|
||||
theme="light" />
|
||||
<NavBar appName={appName} useAuth={useAuth}></NavBar>
|
||||
<NavBar appName={appName}></NavBar>
|
||||
<div id="app-content" className="tw-flex tw-!pl-[77px]">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
//import { useAuth } from "../api/auth";
|
||||
import { useAuth } from "../Auth"
|
||||
import { Link } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import QuestionMarkIcon from '@heroicons/react/24/outline/QuestionMarkCircleIcon'
|
||||
@ -8,7 +8,7 @@ import * as React from "react";
|
||||
import DialogModal from "./DialogModal";
|
||||
|
||||
|
||||
export default function NavBar({ appName, useAuth }: { appName: string, useAuth: any }) {
|
||||
export default function NavBar({ appName}: { appName: string }) {
|
||||
|
||||
|
||||
const [signupOpen, setSignupOpen] = useState(false);
|
||||
@ -110,7 +110,7 @@ export default function NavBar({ appName, useAuth }: { appName: string, useAuth:
|
||||
|
||||
{isAuthenticated ?
|
||||
<div className="tw-flex-none">
|
||||
{user.avatar ? <div className="tw-avatar">
|
||||
{user?.avatar ? <div className="tw-avatar">
|
||||
<div className="tw-w-10 tw-rounded-full">
|
||||
<img src={"https://api.utopia-lab.org/assets/" + user?.avatar + "?access_token=" + token} />
|
||||
</div>
|
||||
@ -122,7 +122,7 @@ export default function NavBar({ appName, useAuth }: { appName: string, useAuth:
|
||||
<path d="M10 6a2 2 0 110-4 2 2 0 010 4zM10 12a2 2 0 110-4 2 2 0 010 4zM10 18a2 2 0 110-4 2 2 0 010 4z" />
|
||||
</svg>
|
||||
</label>
|
||||
<ul tabIndex={0} className="tw-menu tw-menu-compact tw-dropdown-content tw-mt-3 tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-w-52 !tw-z-[1500]">
|
||||
<ul tabIndex={0} className="tw-menu tw-menu-compact tw-dropdown-content tw-mt-3 tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-w-52 !tw-z-[10000]">
|
||||
<li><Link to={"/settings"}>Settings</Link></li>
|
||||
<li><a onClick={() => { onLogout() }}>Logout</a></li>
|
||||
</ul>
|
||||
@ -148,7 +148,7 @@ export default function NavBar({ appName, useAuth }: { appName: string, useAuth:
|
||||
|
||||
|
||||
</label>
|
||||
<ul tabIndex={1} className="tw-menu tw-dropdown-content tw-mt-3 tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-w-52 !tw-z-[1500]">
|
||||
<ul tabIndex={1} className="tw-menu tw-dropdown-content tw-mt-3 tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box tw-w-52 !tw-z-[10000]">
|
||||
<li><a onClick={() => {setLoginOpen(true)}}>Login</a></li>
|
||||
<li><a onClick={() => setSignupOpen(true)}>Sign Up</a></li>
|
||||
</ul>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import {useState, useRef} from 'react'
|
||||
import {useState} from 'react'
|
||||
import {Link} from 'react-router-dom'
|
||||
import ErrorText from '../Typography/ErrorText'
|
||||
import {TextInput} from '../Input/TextInput'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useState, useRef } from 'react'
|
||||
import { useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import ErrorText from '../Typography/ErrorText'
|
||||
import {TextInput} from '../Input/TextInput'
|
||||
|
||||
@ -22,7 +22,7 @@ type AuthContextProps = {
|
||||
login: (credentials: AuthCredentials) => Promise<UserItem | undefined>,
|
||||
register: (credentials: AuthCredentials, userName: string) => Promise<UserItem | undefined>,
|
||||
loading: Boolean,
|
||||
logout: () => void,
|
||||
logout: () => Promise<any>,
|
||||
updateUser: (user: UserItem) => any,
|
||||
token: String | null
|
||||
}
|
||||
@ -33,7 +33,7 @@ const AuthContext = createContext<AuthContextProps>({
|
||||
login: () => Promise.reject(),
|
||||
register: () => Promise.reject(),
|
||||
loading: false,
|
||||
logout: () => { },
|
||||
logout: () => Promise.reject(),
|
||||
updateUser: () => Promise.reject(),
|
||||
token: ""
|
||||
});
|
||||
|
||||
@ -23,7 +23,6 @@ export const Layer = (props: LayerProps) => {
|
||||
|
||||
const searchPhrase = useSearchPhrase();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
resetItems(props);
|
||||
props.data && setItemsData(props);
|
||||
|
||||
@ -1,16 +1,22 @@
|
||||
import * as React from 'react'
|
||||
import { useEffect } from 'react';
|
||||
import { ItemsApi, Permission } from '../../types';
|
||||
import { useSetPermissionData, useSetPermissionApi } from './hooks/usePermissions'
|
||||
import { useSetPermissionData, useSetPermissionApi, useSetAdminRole } from './hooks/usePermissions'
|
||||
import { useAuth } from '../Auth';
|
||||
|
||||
export function Permissions({data, api} : {data?: Permission[], api?: ItemsApi<Permission>}) {
|
||||
export function Permissions({data, api, adminRole} : {data?: Permission[], api?: ItemsApi<Permission>, adminRole?: string}) {
|
||||
const setPermissionData = useSetPermissionData();
|
||||
const setPermissionApi = useSetPermissionApi();
|
||||
const setAdminRole = useSetAdminRole();
|
||||
const {user} = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
console.log(adminRole);
|
||||
|
||||
adminRole && setAdminRole(adminRole);
|
||||
data && setPermissionData(data);
|
||||
api && setPermissionApi(api);
|
||||
}, [api, data])
|
||||
}, [api, data, adminRole, user])
|
||||
|
||||
return (
|
||||
<></>
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
import * as React from 'react'
|
||||
import DynamicHeroIcon from '../../../Utils/DynamicHeroIcon'
|
||||
import { useLayers } from '../hooks/useLayers'
|
||||
import { useHasUserPermission } from '../hooks/usePermissions';
|
||||
|
||||
|
||||
export default function AddButton({ setSelectNewItemPosition }: { setSelectNewItemPosition: React.Dispatch<React.SetStateAction<any>> }) {
|
||||
|
||||
const layers = useLayers();
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
|
||||
|
||||
return (
|
||||
<div className="tw-dropdown tw-dropdown-top tw-dropdown-end tw-dropdown-hover tw-z-500 tw-absolute tw-right-5 tw-bottom-5" >
|
||||
@ -16,7 +19,7 @@ export default function AddButton({ setSelectNewItemPosition }: { setSelectNewIt
|
||||
</label>
|
||||
<ul tabIndex={0} className="tw-dropdown-content tw-pr-1 tw-list-none">
|
||||
{layers.map((layer) => (
|
||||
layer.api?.createItem && (
|
||||
layer.api?.createItem && hasUserPermission(layer.api.collectionName!,"create") && (
|
||||
<li key={layer.name} >
|
||||
<a>
|
||||
<div className="tw-tooltip tw-tooltip-left" data-tip={layer.menuText}>
|
||||
|
||||
@ -5,6 +5,7 @@ import { ItemFormPopupProps } from "../ItemFormPopup";
|
||||
import { LatLng } from "leaflet";
|
||||
import { Item } from "../../../../types";
|
||||
import { toast } from "react-toastify";
|
||||
import { useHasUserPermission, usePermissions } from "../../hooks/usePermissions";
|
||||
|
||||
|
||||
|
||||
@ -17,12 +18,15 @@ export function HeaderView({ item, setItemFormPopup }: {
|
||||
const removeItem = useRemoveItem();
|
||||
|
||||
const map = useMap();
|
||||
const hasUserPermission = useHasUserPermission();
|
||||
const permissions = usePermissions();
|
||||
|
||||
|
||||
const removeItemFromMap = async (event: React.MouseEvent<HTMLElement>) => {
|
||||
setLoading(true);
|
||||
let success = false;
|
||||
try {
|
||||
await item.layer.api?.deleteItem!(item.id)
|
||||
await item.layer?.api?.deleteItem!(item.id)
|
||||
success = true;
|
||||
} catch (error) {
|
||||
toast.error(error.toString());
|
||||
@ -41,16 +45,22 @@ export function HeaderView({ item, setItemFormPopup }: {
|
||||
event.stopPropagation();
|
||||
map.closePopup();
|
||||
if (setItemFormPopup)
|
||||
setItemFormPopup({ position: new LatLng(item.position.coordinates[1], item.position.coordinates[0]), layer: item.layer, item: item, setItemFormPopup: setItemFormPopup })
|
||||
setItemFormPopup({ position: new LatLng(item.position.coordinates[1], item.position.coordinates[0]), layer: item.layer!, item: item, setItemFormPopup: setItemFormPopup })
|
||||
}
|
||||
|
||||
console.log(item.layer.api.collectionName);
|
||||
console.log(permissions);
|
||||
|
||||
console.log( hasUserPermission(item.api?.collectionName!,"update") );
|
||||
|
||||
|
||||
return (
|
||||
<div className='tw-grid tw-grid-cols-6 tw-pb-2'>
|
||||
<div className='tw-col-span-5'>
|
||||
<b className="tw-text-xl tw-font-bold">{item.name}</b>
|
||||
</div>
|
||||
<div className='tw-col-span-1'>
|
||||
{item.layer.api &&
|
||||
{item.layer?.api &&
|
||||
<div className="tw-dropdown tw-dropdown-bottom">
|
||||
<label tabIndex={0} className="tw-bg-base-100 tw-btn tw-m-1 tw-leading-3 tw-border-none tw-min-h-0 tw-h-6">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
@ -58,7 +68,7 @@ export function HeaderView({ item, setItemFormPopup }: {
|
||||
</svg>
|
||||
</label>
|
||||
<ul tabIndex={0} className="tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box">
|
||||
{item.layer.api.updateItem && <li>
|
||||
{item.layer.api.updateItem && hasUserPermission(item.layer.api?.collectionName!,"update") && <li>
|
||||
<a className="!tw-text-base-content" onClick={openEditPopup}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||
@ -66,7 +76,7 @@ export function HeaderView({ item, setItemFormPopup }: {
|
||||
</a>
|
||||
</li>}
|
||||
|
||||
{item.layer.api.deleteItem && <li>
|
||||
{item.layer.api.deleteItem && hasUserPermission(item.layer.api?.collectionName!,"delete") && <li>
|
||||
<a className=' !tw-text-error' onClick={removeItemFromMap}>
|
||||
{loading ? <span className="tw-loading tw-loading-spinner tw-loading-sm"></span>
|
||||
:
|
||||
|
||||
@ -13,6 +13,7 @@ import { TagsProvider } from "./hooks/useTags";
|
||||
import { LayersProvider } from "./hooks/useLayers";
|
||||
import { FilterProvider } from "./hooks/useFilter";
|
||||
import { FilterControl } from "./Subcomponents/FilterControl";
|
||||
import { PermissionsProvider } from "./hooks/usePermissions";
|
||||
|
||||
|
||||
export interface MapEventListenerProps {
|
||||
@ -57,6 +58,7 @@ function UtopiaMap({
|
||||
return (
|
||||
<LayersProvider initialLayers={[]}>
|
||||
<TagsProvider initialTags={[]}>
|
||||
<PermissionsProvider initialPermissions={[]}>
|
||||
<FilterProvider initialTags={[]}>
|
||||
<ItemsProvider initialItems={[]}>
|
||||
<div className={(selectNewItemPosition != null ? "crosshair-cursor-enabled" : undefined)}>
|
||||
@ -88,6 +90,7 @@ function UtopiaMap({
|
||||
</div>
|
||||
</ItemsProvider>
|
||||
</FilterProvider>
|
||||
</PermissionsProvider>
|
||||
</TagsProvider>
|
||||
</LayersProvider>
|
||||
);
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useCallback, useReducer, createContext, useContext } from "react";
|
||||
import * as React from "react";
|
||||
import { ItemsApi, Permission } from "../../../types";
|
||||
import { ItemsApi, LayerProps, Permission, PermissionAction } from "../../../types";
|
||||
import { useAuth } from "../../Auth";
|
||||
|
||||
type ActionType =
|
||||
| { type: "ADD"; permission: Permission }
|
||||
@ -11,13 +12,17 @@ type UsePermissionManagerResult = ReturnType<typeof usePermissionsManager>;
|
||||
const PermissionContext = createContext<UsePermissionManagerResult>({
|
||||
permissions: [],
|
||||
setPermissionApi: () => { },
|
||||
setPermissionData: () => { }
|
||||
setPermissionData: () => { },
|
||||
setAdminRole: () => { },
|
||||
hasUserPermission: () => true
|
||||
});
|
||||
|
||||
function usePermissionsManager(initialPermissions: Permission[]): {
|
||||
permissions: Permission[];
|
||||
setPermissionApi: (api: ItemsApi<Permission>) => void;
|
||||
setPermissionApi: (api: ItemsApi<any>) => void;
|
||||
setPermissionData: (data: Permission[]) => void;
|
||||
setAdminRole: (adminRole: string) => void;
|
||||
hasUserPermission: (collectionName: string, action: PermissionAction) => boolean;
|
||||
} {
|
||||
const [permissions, dispatch] = useReducer((state: Permission[], action: ActionType) => {
|
||||
switch (action.type) {
|
||||
@ -38,10 +43,13 @@ function usePermissionsManager(initialPermissions: Permission[]): {
|
||||
}
|
||||
}, initialPermissions);
|
||||
|
||||
const [api, setApi] = React.useState<ItemsApi<Permission>>({} as ItemsApi<Permission>)
|
||||
const [adminRole, setAdminRole] = React.useState<string | null>(null);
|
||||
const { user } = useAuth();
|
||||
|
||||
|
||||
const setPermissionApi = useCallback(async (api: ItemsApi<Permission>) => {
|
||||
setApi(api);
|
||||
console.log("check");
|
||||
|
||||
const result = await api.getItems();
|
||||
if (result) {
|
||||
result.map(permission => {
|
||||
@ -56,8 +64,17 @@ function usePermissionsManager(initialPermissions: Permission[]): {
|
||||
})
|
||||
}, []);
|
||||
|
||||
const hasUserPermission = useCallback((collectionName: string, action: PermissionAction) => {
|
||||
console.log(permissions);
|
||||
|
||||
return { permissions, setPermissionApi, setPermissionData };
|
||||
if (permissions.length == 0) return true;
|
||||
else if (user && user.role == adminRole) return true;
|
||||
else return permissions.some(p => p.action === action && p.collection === collectionName && p.role == user?.role)
|
||||
}, [permissions, user]);
|
||||
|
||||
|
||||
|
||||
return { permissions, setPermissionApi, setPermissionData, setAdminRole, hasUserPermission };
|
||||
}
|
||||
|
||||
export const PermissionsProvider: React.FunctionComponent<{
|
||||
@ -83,3 +100,13 @@ export const useSetPermissionData = (): UsePermissionManagerResult["setPermissio
|
||||
const { setPermissionData } = useContext(PermissionContext);
|
||||
return setPermissionData;
|
||||
}
|
||||
|
||||
export const useHasUserPermission = (): UsePermissionManagerResult["hasUserPermission"] => {
|
||||
const { hasUserPermission } = useContext(PermissionContext);
|
||||
return hasUserPermission;
|
||||
}
|
||||
|
||||
export const useSetAdminRole = (): UsePermissionManagerResult["setAdminRole"] => {
|
||||
const { setAdminRole } = useContext(PermissionContext);
|
||||
return setAdminRole;
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
export { UtopiaMap } from './UtopiaMap';
|
||||
export { Layer } from './Layer';
|
||||
export { Tags } from "./Tags";
|
||||
export { Permissions } from "./Permissions";
|
||||
export {ItemForm} from './ItemForm';
|
||||
export {ItemView} from './ItemView';
|
||||
export {PopupTextAreaInput} from './Subcomponents/ItemPopupComponents/PopupTextAreaInput';
|
||||
|
||||
@ -4,11 +4,13 @@ import {TextInput} from '../Input/TextInput'
|
||||
import {TextAreaInput} from '../Input/TextAreaInput'
|
||||
import { toast } from 'react-toastify';
|
||||
import {useNavigate} from 'react-router-dom'
|
||||
import { useAuth } from '../Auth';
|
||||
import * as React from 'react'
|
||||
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import { UserItem } from '../../types';
|
||||
|
||||
export function Settings({useAuth}) {
|
||||
export function Settings() {
|
||||
|
||||
const { user, updateUser, loading } = useAuth();
|
||||
|
||||
@ -35,7 +37,7 @@ export function Settings({useAuth}) {
|
||||
|
||||
|
||||
const onUpdateUser = () => {
|
||||
let changedUser = {};
|
||||
let changedUser = {} as UserItem;
|
||||
|
||||
if(passwordChanged) {
|
||||
changedUser = { id: id, first_name: name, description: text, email: email, password: password };
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
export { UtopiaMap, Layer, Tags, ItemForm, ItemView, PopupTextAreaInput, PopupStartEndInput, TextView, StartEndView } from './Components/Map/index';
|
||||
export { UtopiaMap, Layer, Tags, Permissions, ItemForm, ItemView, PopupTextAreaInput, PopupStartEndInput, TextView, StartEndView } from './Components/Map';
|
||||
export {AppShell, Content, SideBar} from "./Components/AppShell"
|
||||
export {AuthProvider, useAuth, LoginPage, SignupPage} from "./Components/Auth"
|
||||
export {Settings} from './Components/Profile'
|
||||
|
||||
@ -66,6 +66,7 @@ export interface ItemsApi<T> {
|
||||
createItem?(item : T): Promise<any>,
|
||||
updateItem?(item : T): Promise<any>,
|
||||
deleteItem?(id : number | string): Promise<any>,
|
||||
collectionName?: string
|
||||
}
|
||||
|
||||
export interface UserApi {
|
||||
@ -79,7 +80,8 @@ export interface UserApi {
|
||||
|
||||
export type UserItem = {
|
||||
id?: string;
|
||||
avatar: string;
|
||||
avatar?: string;
|
||||
role?: string;
|
||||
first_name: string;
|
||||
description: string;
|
||||
email: string;
|
||||
@ -90,5 +92,8 @@ export type Permission = {
|
||||
id?: string;
|
||||
role: string;
|
||||
collection: string;
|
||||
action: string;
|
||||
action: PermissionAction
|
||||
}
|
||||
|
||||
|
||||
export type PermissionAction = "create"|"read"|"update"|"delete";
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user