mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: users management
This commit is contained in:
parent
a552300e15
commit
689625b0d5
22 changed files with 621 additions and 36 deletions
|
|
@ -62,7 +62,7 @@ function ThumbnailUpdate() {
|
|||
<div className='flex justify-center items-center'>
|
||||
<input type="file" id="fileInput" style={{ display: 'none' }} onChange={handleFileChange} />
|
||||
<button
|
||||
className='font-bold antialiased items-center bg-gray-200 text-gray text-sm rounded-md px-4 py-2 mt-4 flex'
|
||||
className='font-bold antialiased items-center text-gray text-sm rounded-md px-4 mt-6 flex'
|
||||
onClick={() => document.getElementById('fileInput')?.click()}
|
||||
>
|
||||
<UploadCloud size={16} className='mr-2' />
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ function BreadCrumbs(props: BreadCrumbsProps) {
|
|||
<div className='text-gray-400 tracking-tight font-medium text-sm flex space-x-1'>
|
||||
<div className='flex items-center space-x-1'>
|
||||
{props.type == 'courses' ? <div className='flex space-x-2 items-center'> <Book className='text-gray' size={14}></Book><Link href='/dash/courses'>Courses</Link></div> : ''}
|
||||
{props.type == 'user' ? <div className='flex space-x-2 items-center'> <User className='text-gray' size={14}></User><Link href='/dash/user/settings/general'>Account Settings</Link></div> : ''}
|
||||
{props.type == 'user' ? <div className='flex space-x-2 items-center'> <User className='text-gray' size={14}></User><Link href='/dash/user-account/settings/general'>Account Settings</Link></div> : ''}
|
||||
{props.type == 'org' ? <div className='flex space-x-2 items-center'> <School className='text-gray' size={14}></School><Link href='/dash/users'>Organization Settings</Link></div> : ''}
|
||||
<div className='flex items-center space-x-1 first-letter:uppercase'>
|
||||
{props.last_breadcrumb ? <ChevronRight size={17} /> : ''}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { useSession } from '@components/Contexts/SessionContext';
|
|||
import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
|
||||
import LearnHouseDashboardLogo from '@public/dashLogo.png';
|
||||
import { logout } from '@services/auth/auth';
|
||||
import { ArrowLeft, Book, BookCopy, Home, LogOut, School, Settings } from 'lucide-react'
|
||||
import { ArrowLeft, Book, BookCopy, Home, LogOut, School, Settings, Users } from 'lucide-react'
|
||||
import Image from 'next/image';
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
|
@ -65,6 +65,9 @@ function LeftMenu() {
|
|||
<ToolTip content={"Courses"} slateBlack sideOffset={8} side='right' >
|
||||
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/courses`} ><BookCopy size={18} /></Link>
|
||||
</ToolTip>
|
||||
<ToolTip content={"Users"} slateBlack sideOffset={8} side='right' >
|
||||
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/users/settings/users`} ><Users size={18} /></Link>
|
||||
</ToolTip>
|
||||
<ToolTip content={"Organization"} slateBlack sideOffset={8} side='right' >
|
||||
<Link className='bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear' href={`/dash/org/settings/general`} ><School size={18} /></Link>
|
||||
</ToolTip>
|
||||
|
|
@ -79,7 +82,7 @@ function LeftMenu() {
|
|||
</ToolTip>
|
||||
<div className='flex items-center flex-col space-y-1'>
|
||||
<ToolTip content={session.user.username + "'s Settings"} slateBlack sideOffset={8} side='right' >
|
||||
<Link href={'/dash/user/settings/general'} className='py-3'>
|
||||
<Link href={'/dash/user-account/settings/general'} className='py-3'>
|
||||
<Settings className='mx-auto text-neutral-400 cursor-pointer' size={18} />
|
||||
</Link>
|
||||
</ToolTip>
|
||||
|
|
|
|||
116
apps/web/components/Dashboard/Users/OrgUsers/OrgUsers.tsx
Normal file
116
apps/web/components/Dashboard/Users/OrgUsers/OrgUsers.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
import PageLoading from '@components/Objects/Loaders/PageLoading';
|
||||
import RolesUpdate from '@components/Objects/Modals/Dash/OrgUsers/RolesUpdate';
|
||||
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal';
|
||||
import Modal from '@components/StyledElements/Modal/Modal';
|
||||
import Toast from '@components/StyledElements/Toast/Toast';
|
||||
import { getAPIUrl } from '@services/config/config';
|
||||
import { removeUserFromOrg } from '@services/organizations/orgs';
|
||||
import { swrFetcher } from '@services/utils/ts/requests';
|
||||
import { KeyRound, LogOut, X } from 'lucide-react';
|
||||
import React, { use, useEffect } from 'react'
|
||||
import toast from 'react-hot-toast';
|
||||
import useSWR, { mutate } from 'swr';
|
||||
|
||||
function OrgUsers() {
|
||||
const org = useOrg() as any;
|
||||
const { data: orgUsers } = useSWR(org ? `${getAPIUrl()}orgs/${org?.id}/users` : null, swrFetcher);
|
||||
const [rolesModal, setRolesModal] = React.useState(false);
|
||||
const [selectedUser, setSelectedUser] = React.useState(null) as any;
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
const handleRolesModal = (user_uuid: any) => {
|
||||
setSelectedUser(user_uuid);
|
||||
setRolesModal(!rolesModal);
|
||||
}
|
||||
|
||||
const handleRemoveUser = async (user_id: any) => {
|
||||
const res = await removeUserFromOrg(org.id, user_id);
|
||||
if (res.status === 200) {
|
||||
await mutate(`${getAPIUrl()}orgs/${org.id}/users`);
|
||||
}
|
||||
else {
|
||||
toast.error('Error ' + res.status + ': ' + res.data.detail)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (orgUsers) {
|
||||
setIsLoading(false)
|
||||
console.log(orgUsers)
|
||||
}
|
||||
}, [org, orgUsers])
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading ? <div><PageLoading /></div> :
|
||||
|
||||
<>
|
||||
<Toast></Toast>
|
||||
<div className="h-6"></div>
|
||||
<div className='ml-10 mr-10 mx-auto bg-white rounded-xl shadow-sm px-4 py-4 '>
|
||||
<table className="table-auto w-full text-left whitespace-nowrap rounded-md overflow-hidden">
|
||||
<thead className='bg-gray-100 text-gray-500 rounded-xl uppercase'>
|
||||
<tr className='font-bolder text-sm'>
|
||||
<th className='py-3 px-4'>User</th>
|
||||
<th className='py-3 px-4'>Role</th>
|
||||
<th className='py-3 px-4'>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<>
|
||||
<tbody className='mt-5 bg-white rounded-md' >
|
||||
{orgUsers?.map((user: any) => (
|
||||
<tr key={user.user.id} className='border-b border-gray-200 border-dashed'>
|
||||
<td className='py-3 px-4 flex space-x-2 items-center'>
|
||||
<span>{user.user.first_name + ' ' + user.user.last_name}</span>
|
||||
<span className='text-xs bg-neutral-100 p-1 px-2 rounded-full text-neutral-400 font-semibold'>@{user.user.username}</span>
|
||||
</td>
|
||||
<td className='py-3 px-4'>{user.role.name}</td>
|
||||
<td className='py-3 px-4 flex space-x-2 items-end'>
|
||||
<Modal
|
||||
isDialogOpen={rolesModal && selectedUser === user.user.user_uuid}
|
||||
onOpenChange={() => handleRolesModal(user.user.user_uuid)}
|
||||
minHeight="no-min"
|
||||
dialogContent={
|
||||
<RolesUpdate
|
||||
alreadyAssignedRole={user.role.role_uuid}
|
||||
setRolesModal={setRolesModal}
|
||||
user={user} />
|
||||
}
|
||||
dialogTitle="Update Role"
|
||||
dialogDescription={"Update @" + user.user.username + "'s role"}
|
||||
dialogTrigger={
|
||||
<button className='flex space-x-2 hover:cursor-pointer p-1 px-3 bg-yellow-700 rounded-md font-bold items-center text-sm text-yellow-100'>
|
||||
<KeyRound className='w-4 h-4' />
|
||||
<span> Edit Role</span>
|
||||
</button>}
|
||||
/>
|
||||
|
||||
|
||||
<ConfirmationModal
|
||||
confirmationButtonText='Remove User'
|
||||
confirmationMessage='Are you sure you want remove this user from the organization?'
|
||||
dialogTitle={'Delete ' + user.user.username + ' ?'}
|
||||
dialogTrigger={
|
||||
<button className='mr-2 flex space-x-2 hover:cursor-pointer p-1 px-3 bg-rose-700 rounded-md font-bold items-center text-sm text-rose-100'>
|
||||
<LogOut className='w-4 h-4' />
|
||||
<span> Remove from organization</span>
|
||||
</button>}
|
||||
functionToExecute={() => { handleRemoveUser(user.user.id) }}
|
||||
status='warning'
|
||||
></ConfirmationModal>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
))}
|
||||
</tbody>
|
||||
</>
|
||||
</table>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default OrgUsers
|
||||
|
|
@ -126,7 +126,7 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
|
|||
<FormField name="course-visibility">
|
||||
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<FormLabel>Course Visibility</FormLabel>
|
||||
<FormMessage match="valueMissing">Please choose cours visibility</FormMessage>
|
||||
<FormMessage match="valueMissing">Please choose course visibility</FormMessage>
|
||||
</Flex>
|
||||
<Form.Control asChild>
|
||||
<select onChange={handleVisibilityChange} className='border border-gray-300 rounded-md p-2' required>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
'use client';
|
||||
import { useOrg } from '@components/Contexts/OrgContext';
|
||||
import FormLayout, { ButtonBlack, Flex, FormField, FormLabel, Input, Textarea } from '@components/StyledElements/Form/Form'
|
||||
import * as Form from '@radix-ui/react-form'
|
||||
import { FormMessage } from "@radix-ui/react-form";
|
||||
import { getAPIUrl } from '@services/config/config';
|
||||
import { updateUserRole } from '@services/organizations/orgs';
|
||||
import { swrFetcher } from '@services/utils/ts/requests';
|
||||
import React, { useEffect } from 'react'
|
||||
import { BarLoader } from 'react-spinners';
|
||||
import { mutate } from 'swr';
|
||||
|
||||
interface Props {
|
||||
user: any
|
||||
setRolesModal: any
|
||||
alreadyAssignedRole: any
|
||||
}
|
||||
|
||||
function RolesUpdate(props: Props) {
|
||||
const org = useOrg() as any;
|
||||
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
||||
const [assignedRole, setAssignedRole] = React.useState(props.alreadyAssignedRole);
|
||||
const [error, setError] = React.useState(null) as any;
|
||||
|
||||
const handleAssignedRole = (event: React.ChangeEvent<any>) => {
|
||||
setError(null);
|
||||
setAssignedRole(event.target.value);
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: any) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
const res = await updateUserRole(org.id, props.user.user.id, assignedRole);
|
||||
|
||||
if (res.status === 200) {
|
||||
await mutate(`${getAPIUrl()}orgs/${org.id}/users`);
|
||||
props.setRolesModal(false);
|
||||
}
|
||||
else {
|
||||
setIsSubmitting(false);
|
||||
setError('Error ' + res.status + ': ' + res.data.detail);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
}
|
||||
, [assignedRole])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormLayout onSubmit={handleSubmit}>
|
||||
<FormField name="course-visibility">
|
||||
{error ? <div className='text-red-500 font-bold text-xs px-3 py-2 bg-red-100 rounded-md'>{error}</div> : ''}
|
||||
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
|
||||
<FormLabel>Roles</FormLabel>
|
||||
<FormMessage match="valueMissing">Please choose a role for the user</FormMessage>
|
||||
</Flex>
|
||||
<Form.Control asChild>
|
||||
<select onChange={handleAssignedRole} defaultValue={assignedRole} className='border border-gray-300 rounded-md p-2' required>
|
||||
<option value="role_global_admin">Admin </option>
|
||||
<option value="role_global_maintainer">Maintainer</option>
|
||||
<option value="role_global_user">User</option>
|
||||
</select>
|
||||
</Form.Control>
|
||||
</FormField>
|
||||
<div className='h-full'></div>
|
||||
<Flex css={{ marginTop: 25, justifyContent: 'flex-end' }}>
|
||||
<Form.Submit asChild>
|
||||
<ButtonBlack type="submit" css={{ marginTop: 10 }}>
|
||||
{isSubmitting ? <BarLoader cssOverride={{ borderRadius: 60, }} width={60} color="#ffffff" />
|
||||
: "Update user role"}
|
||||
</ButtonBlack>
|
||||
</Form.Submit>
|
||||
</Flex>
|
||||
</FormLayout>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default RolesUpdate
|
||||
|
|
@ -5,7 +5,7 @@ import { blackA, violet, mauve } from '@radix-ui/colors';
|
|||
import { Info } from 'lucide-react';
|
||||
|
||||
const FormLayout = (props: any, onSubmit: any) => (
|
||||
<FormRoot onSubmit={props.onSubmit}>
|
||||
<FormRoot className='h-fit' onSubmit={props.onSubmit}>
|
||||
{props.children}
|
||||
</FormRoot>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue