Feat/logged in user data
This commit is contained in:
parent
41df7429d2
commit
595372bef0
15 changed files with 351 additions and 141 deletions
|
|
@ -1,10 +1,11 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import { Disclosure, Menu, Transition } from '@headlessui/react';
|
||||
import { MenuIcon, XIcon } from '@heroicons/react/outline';
|
||||
import { useAuth } from 'src/services/auth';
|
||||
import Gravatar from 'react-gravatar';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import clsx from 'clsx';
|
||||
import { CurrentUserModal } from './components/CurrentUserModal';
|
||||
|
||||
const navigation = [
|
||||
{ name: 'Dashboard', to: '/dashboard' },
|
||||
|
|
@ -19,130 +20,138 @@ function classNames(...classes: any[]) {
|
|||
interface HeaderProps {}
|
||||
|
||||
const Header: React.FC<HeaderProps> = () => {
|
||||
const { logOut } = useAuth();
|
||||
const [currentUserModal, setCurrentUserModal] = useState(false);
|
||||
const { logOut, currentUser } = useAuth();
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
return (
|
||||
<Disclosure as="nav" className="bg-white shadow relative z-10">
|
||||
{({ open }) => (
|
||||
<div className="relative">
|
||||
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
|
||||
<div className="relative flex justify-between h-16">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
|
||||
{/* Mobile menu button */}
|
||||
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500">
|
||||
<span className="sr-only">Open main menu</span>
|
||||
{open ? (
|
||||
<XIcon className="block h-6 w-6" aria-hidden="true" />
|
||||
) : (
|
||||
<MenuIcon className="block h-6 w-6" aria-hidden="true" />
|
||||
)}
|
||||
</Disclosure.Button>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
|
||||
<div className="flex-shrink-0 flex items-center">
|
||||
<img className="block lg:hidden" src="/assets/logo-small.svg" alt="Stackspin" />
|
||||
<img className="hidden lg:block" src="/assets/logo.svg" alt="Stackspin" />
|
||||
</div>
|
||||
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||
{/* Current: "border-primary-500 text-gray-900", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" */}
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.to}
|
||||
className={clsx(
|
||||
'border-primary-50 text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium',
|
||||
{
|
||||
'border-primary-500 text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 text-sm font-medium':
|
||||
pathname.includes(item.to),
|
||||
},
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
|
||||
{/* Profile dropdown */}
|
||||
<Menu as="div" className="ml-3 relative">
|
||||
<div>
|
||||
<Menu.Button className="bg-white rounded-full flex text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
|
||||
<span className="sr-only">Open user menu</span>
|
||||
<span className="inline-flex items-center justify-center h-8 w-8 rounded-full bg-gray-500 overflow-hidden">
|
||||
<Gravatar email="" size={32} rating="pg" protocol="https://" />
|
||||
</span>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
onClick={() => logOut()}
|
||||
className={classNames(
|
||||
active ? 'bg-gray-100 cursor-pointer' : '',
|
||||
'block px-4 py-2 text-sm text-gray-700 cursor-pointer',
|
||||
)}
|
||||
>
|
||||
Configure profile
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
onClick={() => logOut()}
|
||||
className={classNames(
|
||||
active ? 'bg-gray-100 cursor-pointer' : '',
|
||||
'block px-4 py-2 text-sm text-gray-700 cursor-pointer',
|
||||
)}
|
||||
>
|
||||
Sign out
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
const currentUserModalOpen = () => {
|
||||
setCurrentUserModal(true);
|
||||
};
|
||||
const currentUserModalClose = () => setCurrentUserModal(false);
|
||||
|
||||
<Disclosure.Panel className="sm:hidden">
|
||||
<div className="pt-2 pb-4 space-y-1">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.to}
|
||||
// getProps={({ isCurrent }) => {
|
||||
// // the object returned here is passed to the
|
||||
// // anchor element's props
|
||||
// return {
|
||||
// className: isCurrent
|
||||
// ? 'bg-primary-50 border-primary-400 text-primary-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium'
|
||||
// : 'border-transparent text-gray-500 hover:bg-gray-50 hover:border-gray-300 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium',
|
||||
// 'aria-current': isCurrent ? 'page' : undefined,
|
||||
// };
|
||||
// }}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
return (
|
||||
<>
|
||||
<Disclosure as="nav" className="bg-white shadow relative z-10">
|
||||
{({ open }) => (
|
||||
<div className="relative">
|
||||
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
|
||||
<div className="relative flex justify-between h-16">
|
||||
<div className="absolute inset-y-0 left-0 flex items-center sm:hidden">
|
||||
{/* Mobile menu button */}
|
||||
<Disclosure.Button className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500">
|
||||
<span className="sr-only">Open main menu</span>
|
||||
{open ? (
|
||||
<XIcon className="block h-6 w-6" aria-hidden="true" />
|
||||
) : (
|
||||
<MenuIcon className="block h-6 w-6" aria-hidden="true" />
|
||||
)}
|
||||
</Disclosure.Button>
|
||||
</div>
|
||||
<div className="flex-1 flex items-center justify-center sm:items-stretch sm:justify-start">
|
||||
<Link to="/" className="flex-shrink-0 flex items-center">
|
||||
<img className="block lg:hidden" src="/assets/logo-small.svg" alt="Stackspin" />
|
||||
<img className="hidden lg:block" src="/assets/logo.svg" alt="Stackspin" />
|
||||
</Link>
|
||||
<div className="hidden sm:ml-6 sm:flex sm:space-x-8">
|
||||
{/* Current: "border-primary-500 text-gray-900", Default: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700" */}
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.to}
|
||||
className={clsx(
|
||||
'border-primary-50 text-gray-900 inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium',
|
||||
{
|
||||
'border-primary-500 text-gray-500 hover:border-gray-300 hover:text-gray-700 inline-flex items-center px-1 pt-1 text-sm font-medium':
|
||||
pathname.includes(item.to),
|
||||
},
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
|
||||
{/* Profile dropdown */}
|
||||
<Menu as="div" className="ml-3 relative">
|
||||
<div>
|
||||
<Menu.Button className="bg-white rounded-full flex text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary">
|
||||
<span className="sr-only">Open user menu</span>
|
||||
<span className="inline-flex items-center justify-center h-8 w-8 rounded-full bg-gray-500 overflow-hidden">
|
||||
<Gravatar email={currentUser.email || ''} size={32} rating="pg" protocol="https://" />
|
||||
</span>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-200"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items className="origin-top-right absolute right-0 mt-2 w-48 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
onClick={() => currentUserModalOpen()}
|
||||
className={classNames(
|
||||
active ? 'bg-gray-100 cursor-pointer' : '',
|
||||
'block px-4 py-2 text-sm text-gray-700 cursor-pointer',
|
||||
)}
|
||||
>
|
||||
Configure profile
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
onClick={() => logOut()}
|
||||
className={classNames(
|
||||
active ? 'bg-gray-100 cursor-pointer' : '',
|
||||
'block px-4 py-2 text-sm text-gray-700 cursor-pointer',
|
||||
)}
|
||||
>
|
||||
Sign out
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</div>
|
||||
)}
|
||||
</Disclosure>
|
||||
|
||||
<Disclosure.Panel className="sm:hidden">
|
||||
<div className="pt-2 pb-4 space-y-1">
|
||||
{navigation.map((item) => (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.to}
|
||||
className={clsx(
|
||||
'border-transparent text-gray-500 hover:text-gray-700 block pl-3 pr-4 py-2 border-l-4 text-base font-medium',
|
||||
{
|
||||
'bg-primary-50 border-primary-400 text-primary-700 block pl-3 pr-4 py-2': pathname.includes(
|
||||
item.to,
|
||||
),
|
||||
},
|
||||
)}
|
||||
>
|
||||
{item.name}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</Disclosure.Panel>
|
||||
</div>
|
||||
)}
|
||||
</Disclosure>
|
||||
|
||||
<CurrentUserModal open={currentUserModal} onClose={currentUserModalClose} user={currentUser} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Modal, Banner } from 'src/components';
|
||||
import { Input } from 'src/components/Form';
|
||||
import { useUsers } from 'src/services/users';
|
||||
import { CurrentUser } from 'src/services/auth';
|
||||
import { appAccessList } from './consts';
|
||||
import { UserModalProps } from './types';
|
||||
|
||||
export const CurrentUserModal = ({ open, onClose, user }: UserModalProps) => {
|
||||
const { editUserById, userModalLoading } = useUsers();
|
||||
|
||||
const { control, reset, handleSubmit } = useForm<CurrentUser>({
|
||||
defaultValues: {
|
||||
name: null,
|
||||
email: null,
|
||||
id: null,
|
||||
preferredUsername: null,
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
reset(user);
|
||||
}
|
||||
|
||||
return () => {
|
||||
reset({ name: null, email: null, id: null });
|
||||
};
|
||||
}, [user, reset]);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
if (user) {
|
||||
await handleSubmit((data) => editUserById(data))();
|
||||
}
|
||||
|
||||
onClose();
|
||||
} catch (e: any) {
|
||||
// Continue
|
||||
}
|
||||
};
|
||||
|
||||
const handleKeyPress = (e: any) => {
|
||||
if (e.key === 'Enter' || e.key === 'NumpadEnter') {
|
||||
handleSave();
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal onClose={handleClose} open={open} onSave={handleSave} isLoading={userModalLoading} useCancelButton>
|
||||
<div className="bg-white px-4">
|
||||
<div className="space-y-4 divide-y divide-gray-200">
|
||||
<div>
|
||||
<div>
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">Profile</h3>
|
||||
</div>
|
||||
|
||||
<div className="mt-6 grid grid-cols-1 gap-y-6 gap-x-4 sm:grid-cols-6">
|
||||
<div className="sm:col-span-3">
|
||||
<Input control={control} name="name" label="Name" onKeyPress={handleKeyPress} />
|
||||
</div>
|
||||
|
||||
<div className="sm:col-span-3">
|
||||
<Input control={control} name="email" label="Email" type="email" onKeyPress={handleKeyPress} />
|
||||
</div>
|
||||
|
||||
<div className="sm:col-span-6">
|
||||
<Banner title="Editing user status, roles and app access coming soon." titleSm="Comming soon!" />
|
||||
</div>
|
||||
|
||||
<div className="sm:col-span-3 opacity-40 cursor-default pointer-events-none select-none">
|
||||
{/* <Select control={control} name="status" label="Status" options={['Active', 'Inactive']} /> */}
|
||||
<label htmlFor="status" className="block text-sm font-medium text-gray-700">
|
||||
Status
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<select
|
||||
id="status"
|
||||
name="status"
|
||||
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
>
|
||||
<option>Active</option>
|
||||
<option>Inactive</option>
|
||||
<option>Banned</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="sm:col-span-3 opacity-40 cursor-default pointer-events-none select-none">
|
||||
<label htmlFor="status" className="block text-sm font-medium text-gray-700">
|
||||
Role
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<select
|
||||
id="status"
|
||||
name="status"
|
||||
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
>
|
||||
<option>User</option>
|
||||
<option>Admin</option>
|
||||
<option>Super Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="opacity-40 cursor-default pointer-events-none select-none">
|
||||
<div className="mt-4">
|
||||
<h3 className="text-lg leading-6 font-medium text-gray-900">App Access</h3>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="flow-root mt-6">
|
||||
<ul className="-my-5 divide-y divide-gray-200 ">
|
||||
{appAccessList.map((app: any) => {
|
||||
return (
|
||||
<li className="py-4" key={app.name}>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div className="flex-shrink-0 flex-1 flex items-center">
|
||||
<img className="h-10 w-10 rounded-md overflow-hidden" src={app.image} alt={app.name} />
|
||||
<h3 className="ml-4 text-md leading-6 font-medium text-gray-900">{app.name}</h3>
|
||||
</div>
|
||||
<div>
|
||||
<select
|
||||
id={app.name}
|
||||
name={app.name}
|
||||
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-gray-300 rounded-md"
|
||||
>
|
||||
<option>User</option>
|
||||
<option>Admin</option>
|
||||
<option>Super Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
18
src/components/Header/components/CurrentUserModal/consts.ts
Normal file
18
src/components/Header/components/CurrentUserModal/consts.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
export const appAccessList = [
|
||||
{
|
||||
image: '/assets/wekan.svg',
|
||||
name: 'Wekan',
|
||||
},
|
||||
{
|
||||
image: '/assets/wordpress.svg',
|
||||
name: 'Wordpress',
|
||||
},
|
||||
{
|
||||
image: '/assets/nextcloud.svg',
|
||||
name: 'NextCloud',
|
||||
},
|
||||
{
|
||||
image: '/assets/zulip.svg',
|
||||
name: 'Zulip',
|
||||
},
|
||||
];
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { CurrentUserModal } from './CurrentUserModal';
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { CurrentUser } from 'src/services/auth';
|
||||
|
||||
export type UserModalProps = {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
user: CurrentUser;
|
||||
};
|
||||
1
src/components/Header/components/index.ts
Normal file
1
src/components/Header/components/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { CurrentUserModal } from './CurrentUserModal';
|
||||
Reference in a new issue