diff --git a/frontend/src/pages/profile.tsx b/frontend/src/pages/profile.tsx index f5eb7cf..0448bb2 100644 --- a/frontend/src/pages/profile.tsx +++ b/frontend/src/pages/profile.tsx @@ -1,180 +1,265 @@ -import { - mdiChartTimelineVariant, - mdiUpload, -} from '@mdi/js'; -import Head from 'next/head'; -import React, { ReactElement, useEffect, useState } from 'react'; -import { ToastContainer, toast } from 'react-toastify'; -import DatePicker from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; - -import CardBox from '../components/CardBox'; -import LayoutAuthenticated from '../layouts/Authenticated'; -import SectionMain from '../components/SectionMain'; -import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'; -import { getPageTitle } from '../config'; - +import { mdiUpload } from '@mdi/js'; import { Field, Form, Formik } from 'formik'; -import FormField from '../components/FormField'; -import BaseDivider from '../components/BaseDivider'; -import BaseButtons from '../components/BaseButtons'; -import BaseButton from '../components/BaseButton'; -import FormCheckRadio from '../components/FormCheckRadio'; -import FormCheckRadioGroup from '../components/FormCheckRadioGroup'; -import FormImagePicker from '../components/FormImagePicker'; -import { SwitchField } from '../components/SwitchField'; -import { SelectField } from '../components/SelectField'; - -import { update, fetch } from '../stores/users/usersSlice'; -import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import Head from 'next/head'; +import Image from 'next/image'; import { useRouter } from 'next/router'; -import {findMe} from "../stores/authSlice"; +import React, { ReactElement, useEffect, useState } from 'react'; +import { toast } from 'react-toastify'; +import FormImagePicker from '../components/FormImagePicker'; +import { SelectField } from '../components/SelectField'; +import { SwitchField } from '../components/SwitchField'; +import { getPageTitle } from '../config'; +import LayoutAuthenticated from '../layouts/Authenticated'; +import { findMe } from '../stores/authSlice'; +import { useAppDispatch, useAppSelector } from '../stores/hooks'; +import { update } from '../stores/users/usersSlice'; + +type ProfileValues = { + firstName: string; + lastName: string; + phoneNumber: string; + email: string; + app_role: any; + disabled: boolean; + avatar: any[]; + password: string; +}; + +const initVals: ProfileValues = { + firstName: '', + lastName: '', + phoneNumber: '', + email: '', + app_role: '', + disabled: false, + avatar: [], + password: '', +}; + +function Panel({ + children, + className = '', +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+ {children} +
+ ); +} + +function FieldWrap({ + label, + children, +}: { + label: string; + children: React.ReactNode; +}) { + return ( + + ); +} + +const inputClassName = + 'w-full rounded-lg border border-[#19192d]/10 bg-[#fffdf9] px-3 py-2 text-sm text-[#19192d] outline-none focus:border-[#35b7a5] focus:ring-2 focus:ring-[#35b7a5]/15 disabled:bg-[#fbf8f1] disabled:text-[#72798a]'; const EditUsers = () => { - const { currentUser, isFetching, token } = useAppSelector( - (state) => state.auth, - ); - const router = useRouter(); - const dispatch = useAppDispatch(); - const notify = (type, msg) => toast(msg, { type }); - const initVals = { - firstName: '', - lastName: '', - phoneNumber: '', - email: '', - app_role: '', - disabled: false, - avatar: [], - password: '' - }; - const [initialValues, setInitialValues] = useState(initVals); + const { currentUser } = useAppSelector((state) => state.auth); + const router = useRouter(); + const dispatch = useAppDispatch(); + const [initialValues, setInitialValues] = useState(initVals); - useEffect(() => { - if (currentUser?.id && typeof currentUser === 'object') { - const newInitialVal = { ...initVals }; + useEffect(() => { + if (currentUser?.id && typeof currentUser === 'object') { + const newInitialVal = { ...initVals } as Record; - Object.keys(initVals).forEach( - (el) => (newInitialVal[el] = currentUser[el]), - ); + Object.keys(initVals).forEach((key) => { + newInitialVal[key] = currentUser[key]; + }); - setInitialValues(newInitialVal); - } - }, [currentUser]); + setInitialValues(newInitialVal); + } + }, [currentUser]); - const handleSubmit = async (data) => { - await dispatch(update({ id: currentUser.id, data })); - await dispatch(findMe()); - await router.push('/users/users-list'); - notify('success', 'Profile was updated!'); - }; + const handleSubmit = async (data: ProfileValues) => { + await dispatch(update({ id: currentUser.id, data })); + await dispatch(findMe()); + await router.push('/users/users-list'); + toast('Profile was updated!', { type: 'success' }); + }; - return ( - <> - - {getPageTitle('Edit profile')} - - - + + {getPageTitle('Edit profile')} + + +
+
+
+ + + Account + +
+

Profile

+

+ Update your workspace profile, avatar, role, and password. +

+
+ + + handleSubmit(values)} + > +
+
+
+

Avatar

+
+ {avatarUrl ? ( + Avatar + ) : ( + + {currentUser?.firstName?.[0] || + currentUser?.email?.[0] || + 'U'} + + )} +
+
+ +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+ +
+ + + +
+ +
+
+
+ + ); }; EditUsers.getLayout = function getLayout(page: ReactElement) { - return {page}; + return {page}; }; export default EditUsers; diff --git a/frontend/src/pages/users/users-list.tsx b/frontend/src/pages/users/users-list.tsx index 2dbbc69..bb5c321 100644 --- a/frontend/src/pages/users/users-list.tsx +++ b/frontend/src/pages/users/users-list.tsx @@ -1,166 +1,201 @@ -import { mdiChartTimelineVariant } from '@mdi/js' -import Head from 'next/head' +import axios from 'axios'; +import Head from 'next/head'; +import React, { ReactElement, useState } from 'react'; import { uniqueId } from 'lodash'; -import React, { ReactElement, useState } from 'react' -import CardBox from '../../components/CardBox' -import LayoutAuthenticated from '../../layouts/Authenticated' -import SectionMain from '../../components/SectionMain' -import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton' -import { getPageTitle } from '../../config' -import TableUsers from '../../components/Users/TableUsers' -import BaseButton from '../../components/BaseButton' -import axios from "axios"; -import Link from "next/link"; -import {useAppDispatch, useAppSelector} from "../../stores/hooks"; -import CardBoxModal from "../../components/CardBoxModal"; -import DragDropFilePicker from "../../components/DragDropFilePicker"; -import {setRefetch, uploadCsv} from '../../stores/users/usersSlice'; +import CardBoxModal from '../../components/CardBoxModal'; +import DragDropFilePicker from '../../components/DragDropFilePicker'; +import TableUsers from '../../components/Users/TableUsers'; +import { getPageTitle } from '../../config'; +import { hasPermission } from '../../helpers/userPermissions'; +import LayoutAuthenticated from '../../layouts/Authenticated'; +import { useAppDispatch, useAppSelector } from '../../stores/hooks'; +import { setRefetch, uploadCsv } from '../../stores/users/usersSlice'; +function Panel({ + children, + className = '', +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+ {children} +
+ ); +} -import {hasPermission} from "../../helpers/userPermissions"; +function ActionButton({ + children, + href, + onClick, + variant = 'primary', +}: { + children: React.ReactNode; + href?: string; + onClick?: () => void; + variant?: 'primary' | 'secondary'; +}) { + const className = + variant === 'primary' + ? 'rounded-full bg-[#35b7a5] px-4 py-2 text-sm font-semibold text-white' + : 'rounded-full border border-[#19192d]/10 bg-white px-4 py-2 text-sm font-semibold text-[#19192d]'; + if (href) { + return ( + + {children} + + ); + } + return ( + + ); +} const UsersTablesPage = () => { const [filterItems, setFilterItems] = useState([]); const [csvFile, setCsvFile] = useState(null); const [isModalActive, setIsModalActive] = useState(false); - const [showTableView, setShowTableView] = useState(false); - const { currentUser } = useAppSelector((state) => state.auth); - - const dispatch = useAppDispatch(); - - const [filters] = useState([{label: 'First Name', title: 'firstName'},{label: 'Last Name', title: 'lastName'},{label: 'Phone Number', title: 'phoneNumber'},{label: 'E-Mail', title: 'email'}, - - - - - - {label: 'App Role', title: 'app_role'}, - - - {label: 'Custom Permissions', title: 'custom_permissions'}, - + const [filters] = useState([ + { label: 'First Name', title: 'firstName' }, + { label: 'Last Name', title: 'lastName' }, + { label: 'Phone Number', title: 'phoneNumber' }, + { label: 'E-Mail', title: 'email' }, + { label: 'App Role', title: 'app_role' }, + { label: 'Custom Permissions', title: 'custom_permissions' }, ]); - - const hasCreatePermission = currentUser && hasPermission(currentUser, 'CREATE_USERS'); - - const addFilter = () => { - const newItem = { - id: uniqueId(), - fields: { - filterValue: '', - filterValueFrom: '', - filterValueTo: '', - selectedField: '', - }, - }; - newItem.fields.selectedField = filters[0].title; - setFilterItems([...filterItems, newItem]); - }; + const hasCreatePermission = + currentUser && hasPermission(currentUser, 'CREATE_USERS'); - const getUsersCSV = async () => { - const response = await axios({url: '/users?filetype=csv', method: 'GET',responseType: 'blob'}); - const type = response.headers['content-type'] - const blob = new Blob([response.data], { type: type }) - const link = document.createElement('a') - link.href = window.URL.createObjectURL(blob) - link.download = 'usersCSV.csv' - link.click() + const addFilter = () => { + const newItem = { + id: uniqueId(), + fields: { + filterValue: '', + filterValueFrom: '', + filterValueTo: '', + selectedField: filters[0].title, + }, }; + setFilterItems([...filterItems, newItem]); + }; - const onModalConfirm = async () => { - if (!csvFile) return; - await dispatch(uploadCsv(csvFile)); - dispatch(setRefetch(true)); - setCsvFile(null); - setIsModalActive(false); - }; + const getUsersCSV = async () => { + const response = await axios({ + url: '/users?filetype=csv', + method: 'GET', + responseType: 'blob', + }); + const type = response.headers['content-type']; + const blob = new Blob([response.data], { type }); + const link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = 'usersCSV.csv'; + link.click(); + }; - const onModalCancel = () => { - setCsvFile(null); - setIsModalActive(false); - }; + const onModalConfirm = async () => { + if (!csvFile) { + return; + } + + await dispatch(uploadCsv(csvFile)); + dispatch(setRefetch(true)); + setCsvFile(null); + setIsModalActive(false); + }; + + const onModalCancel = () => { + setCsvFile(null); + setIsModalActive(false); + }; return ( <> {getPageTitle('Users')} - - - {''} - - - - {hasCreatePermission && } - - - - + +
+
+
+ + + Team access + +
+

Users

+

+ Manage workspace members, roles, and export or import user records. +

+
+ + +
{hasCreatePermission && ( - setIsModalActive(true)} - /> + Add user )} - -
-
-
- - - - + + Filter + + + Download CSV + + {hasCreatePermission && ( + setIsModalActive(true)} + > + Upload CSV + + )} +
+
+
+ + - - - - - + +
+ + + - ) -} + ); +}; UsersTablesPage.getLayout = function getLayout(page: ReactElement) { return ( - - {page} - - ) -} + {page} + ); +}; -export default UsersTablesPage +export default UsersTablesPage;