diff --git a/components/Forms/CreateAdminPopupWindow.tsx b/components/Forms/CreateAdminPopupWindow.tsx new file mode 100644 index 0000000..2488c6e --- /dev/null +++ b/components/Forms/CreateAdminPopupWindow.tsx @@ -0,0 +1,146 @@ +import { + AboutEvent, + FormLogistics, + ShortFormInput, + LongFormInput, + LargeFormInput, + InputFlex, + FormInput, + FormLabel, + CreateEventForm, + FormHeader, + CreateEventContainer, +} from '@/styles/components/event.styles'; +import { + SubmitButton, + ButtonCenter, +} from '@/styles/components/windowFlow.styles'; +import React, { useState } from 'react'; +import PopupWindow from '@/components/PopupWindow'; +import Dayjs from 'dayjs'; +import { DatePicker, TimePicker, message } from 'antd'; +import { useForm } from 'react-hook-form'; + +const CreateEventPopupWindow = ({ + setShowPopup, + messageApi, +}: { + setShowPopup: React.Dispatch>; + messageApi: any; +}) => { + const { RangePicker } = DatePicker; + + const [startTime, setStartTime] = useState('12:00'); + const [endTime, setEndTime] = useState('12:00'); + + const { register, handleSubmit } = useForm({}); + + const onSubmit = (data: any) => { + const results = JSON.stringify({ + firstName: data.firstName, + lastName: data.lastName, + email: data.email, + phone: data.phone, + role: data.role, + password: data.password, + }); + console.log('Here are the results: ', results); + createAdmin(results); + }; + + const createAdmin = async (data: any) => { + const res = await fetch('/api/admin', { + method: 'POST', + headers: { + // Specify the content type in the headers + 'Content-Type': 'application/json', + }, + // Stringify the JSON body + body: data, + }); + + if (res.ok) { + const resObj = await res.json(); + messageApi.open({ + type: 'success', + content: resObj.message, + }); + } else { + const resObj = await res.json(); + messageApi.open({ + type: 'error', + content: resObj.message, + }); + } + + setShowPopup(false); + }; + + return ( + <> + setShowPopup(false)}> + + + Add new account + + + + + + + + + + + + + + + Add + + + + + + ); +}; + +export default CreateEventPopupWindow; diff --git a/components/table/AdminTable.tsx b/components/table/admin-table/AdminTable.tsx similarity index 50% rename from components/table/AdminTable.tsx rename to components/table/admin-table/AdminTable.tsx index 01eafac..7b577fa 100644 --- a/components/table/AdminTable.tsx +++ b/components/table/admin-table/AdminTable.tsx @@ -1,41 +1,19 @@ import React, { useState, useEffect, useRef } from 'react'; -import { Button, Input, Space, Tag, Table, TableProps, InputRef } from 'antd'; +import { Button, Input, Space, TableProps, InputRef } from 'antd'; import type { ColumnType, FilterValue, SorterResult, } from 'antd/es/table/interface'; -import { - PlusOutlined, - SearchOutlined, - MinusOutlined, - ArrowRightOutlined, - UserOutlined, -} from '@ant-design/icons'; -import * as XLSX from 'xlsx'; -import { saveAs } from 'file-saver'; +import { SearchOutlined } from '@ant-design/icons'; import useSWR from 'swr'; import Highlighter from 'react-highlight-words'; import { QueriedAdminData } from 'bookem-shared/src/types/database'; -import { SearchContainter, TableContainer } from '@/styles/table.styles'; -import Link from 'next/link'; -import { ObjectId } from 'mongodb'; -import EventPopupWindowForm from '@/components/Forms/EventPopupWindowForm'; +import { convertAdminDataToRowData } from '@/utils/table-utils'; import type { FilterConfirmProps } from 'antd/es/table/interface'; - -interface AdminRowData { - key: number; - firstName: string; - lastName: string; - email: string; - phone: string; - role: string; - id: ObjectId; -} - -type DataIndex = keyof AdminRowData; - -const fetcher = (url: string) => fetch(url).then(res => res.json()); +import { AdminDataIndex, AdminRowData } from '@/utils/table-types'; +import AdminTableImpl from './AdminTableImpl'; +import { fetcher } from '@/utils/utils'; const AdminTable = () => { const { data, error, isLoading } = useSWR( @@ -66,7 +44,7 @@ const AdminTable = () => { const handleSearch = ( selectedKeys: string[], confirm: (param?: FilterConfirmProps) => void, - dataIndex: DataIndex + dataIndex: AdminDataIndex ) => { confirm(); setSearchText(selectedKeys[0]); @@ -80,7 +58,7 @@ const AdminTable = () => { */ // Function to get column search properties const getColumnSearchProps = ( - dataIndex: DataIndex + dataIndex: AdminDataIndex ): ColumnType => ({ // Filter dropdown configuration filterDropdown: ({ @@ -123,17 +101,6 @@ const AdminTable = () => { style={{ width: 90 }}> Reset - {/* Filter and close buttons */} - - - ), - }, - ]; - return ( <> - {showPopup && } - - - (e.target.value ? [e.target.value] : [])} - prefix={} - style={{ width: 1000, display: 'flex' }} - /> - - - - - - -
- - - + ); }; - -// const convertEventDataToRowData = (data: QueriedVolunteerEventDTO[]) => { -// const result = data.map((event, index) => { -// return { -// key: index, -// firstName: 'Manish', -// lastName: 'Acharya', -// email: 'manish@gmail.com', -// phone: '1234567890', -// role: 'Super Admin', -// }; -// }); -// return result; -// }; - -const convertAdminDataToRowData = (data: QueriedAdminData[]) => { - const result = data.map((user, index) => { - return { - key: index, - firstName: user.firstName, - lastName: user.lastName, - email: user.email, - phone: user.phone, - role: user.status, - id: user._id, - }; - }); - return result; -}; - export default AdminTable; diff --git a/components/table/admin-table/AdminTableImpl.tsx b/components/table/admin-table/AdminTableImpl.tsx new file mode 100644 index 0000000..21a9377 --- /dev/null +++ b/components/table/admin-table/AdminTableImpl.tsx @@ -0,0 +1,115 @@ +import React, { useState } from 'react'; +import { Button, Table, TableProps, message } from 'antd'; +import type { + ColumnType, + ColumnsType, + SorterResult, +} from 'antd/es/table/interface'; +import { ArrowRightOutlined } from '@ant-design/icons'; +import { TableContainer } from '@/styles/table.styles'; +import CreateAdminPopupWindow from '@/components/Forms/CreateAdminPopupWindow'; +import { AdminDataIndex, AdminRowData } from '@/utils/table-types'; +import TableHeader from '../admin-table/TableHeader'; + +/** + * Contains the "UI" part of Event Table + * @returns + */ +const AdminTableImpl = ({ + getColumnSearchProps, + sortedInfo, + handleChange, + dataForTable, + searchInput, +}: { + getColumnSearchProps: (dataIndex: AdminDataIndex) => ColumnType; + sortedInfo: SorterResult; + handleChange: TableProps['onChange']; + dataForTable: AdminRowData[]; + searchInput: React.RefObject; +}) => { + const [showPopup, setShowPopup] = useState(false); + const [showPopupTag, setShowPopupTag] = useState(false); + const [messageApi, contextHolder] = message.useMessage(); + + // Define columns for the Ant Design table + const columns: ColumnsType = [ + { + title: 'First Name', + dataIndex: 'firstName', + key: 'firstName', + ...getColumnSearchProps('firstName'), + }, + { + title: 'Last Name', + dataIndex: 'lastName', + key: 'lastName', + ...getColumnSearchProps('lastName'), + }, + { + title: 'Email', + dataIndex: 'email', + key: 'email', + ...getColumnSearchProps('email'), + }, + { + title: 'Phone', + dataIndex: 'phone', + key: 'phone', + ...getColumnSearchProps('phone'), + }, + { title: 'Role', dataIndex: 'status', key: 'status' }, + { + title: 'View', + dataIndex: '', + render: () => ( + + + + ), + }, + ]; + + return ( + <> + {contextHolder} + {showPopup && ( + + )} + + + +
+
+ + + + ); +}; + +export default AdminTableImpl; diff --git a/components/table/admin-table/TableHeader.tsx b/components/table/admin-table/TableHeader.tsx new file mode 100644 index 0000000..7263e2c --- /dev/null +++ b/components/table/admin-table/TableHeader.tsx @@ -0,0 +1,65 @@ +import { SearchContainter, TableButton } from '@/styles/table.styles'; +import React, { useRef } from 'react'; +import { Button, Input, InputRef } from 'antd'; +import { + PlusOutlined, + SearchOutlined, + MinusOutlined, + UserOutlined, +} from '@ant-design/icons'; + +const TableHeader = ({ + setShowPopup, + showPopup, + setShowPopupTag, + showPopupTag, + searchInput, +}: { + setShowPopup: (a: boolean) => void; + showPopup: boolean; + setShowPopupTag: (a: boolean) => void; + showPopupTag: boolean; + searchInput: React.RefObject; +}) => { + return ( + <> +
+ + + + + +
+ + ); +}; + +export default TableHeader; diff --git a/pages/api/admin/index.ts b/pages/api/admin/index.ts index 2bbb1f3..4b65433 100644 --- a/pages/api/admin/index.ts +++ b/pages/api/admin/index.ts @@ -5,6 +5,7 @@ import dbConnect from '@/lib/dbConnect'; import { getSession } from 'next-auth/react'; import Admins from 'bookem-shared/src/models/Admins'; import { QueriedAdminData, AdminData } from 'bookem-shared/src/types/database'; +import { hash } from 'bcrypt'; export default async function handler( req: NextApiRequest, @@ -24,8 +25,59 @@ export default async function handler( error: 'Sorry, an error occurred while connecting to the database', }); } - break; + case 'POST': + try { + // start a try catch block to catch any errors in parsing the request body + const admin = req.body as QueriedAdminData; + + console.log('Here is the request body: ', admin); + + // Get the user's email and password from the request body + const { firstName, lastName, email, password } = admin; + + // Check if the user's email and password are valid + if (!email || !email.includes('@') || !password) { + res.status(422).json({ message: 'Invalid email or password' }); + throw new Error('Invalid email or password'); + } + + // Connect to the database + await dbConnect(); + + // Check if the user already exists in database + const checkExisting = (await Admins.findOne({ + email, + })) as QueriedAdminData; + + // If the user already exists, return an error + if (checkExisting) { + res.status(422).json({ message: 'Admin email already exists' }); + throw new Error('Admin email already exists'); + } + + // Hash the user's password + const hashedPassword = await hash(password, 12); + + // construct the user object to insert into the database + let adminToInsert: AdminData = { + firstName, + lastName, + email, + password: hashedPassword, + phone: admin.phone, + status: admin.status, + }; + + // Create a new user in the database + const status = await Admins.insertMany(adminToInsert); + + // Return the status of the user creation + res.status(201).json({ message: 'Admin created', ...status }); + } catch (e) { + console.log('Here is the error: ', e); + res.status(500).json({ message: 'An error occurred', error: e }); + } default: res.status(405).json({ error: 'Sorry, only GET requests are supported', diff --git a/pages/api/create.ts b/pages/api/create.ts new file mode 100644 index 0000000..9f25c96 --- /dev/null +++ b/pages/api/create.ts @@ -0,0 +1,77 @@ +import dbConnect from '@/lib/dbConnect'; +import { hash } from 'bcrypt'; +import type { NextApiRequest, NextApiResponse } from 'next'; +import Admins from 'bookem-shared/src/models/Admins'; +import { QueriedAdminData, AdminData } from 'bookem-shared/src/types/database'; +import { getServerSession } from 'next-auth'; +import { authOptions } from './auth/[...nextauth]'; + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + switch (req.method) { + case 'POST': + try { + //Get current session of the user an check if the user is superadmin + const session = await getServerSession(req, res, authOptions); + + if (session.user.status !== 'superadmin') { + res.status(401).json({ message: 'Not authorized' }); + throw new Error('Not authorized'); + } + + // start a try catch block to catch any errors in parsing the request body + const admin = req.body as QueriedAdminData; + + // Get the user's email and password from the request body + const { firstName, lastName, email, password } = admin; + + // Check if the user's email and password are valid + if (!email || !email.includes('@') || !password) { + res.status(422).json({ message: 'Invalid email or password' }); + throw new Error('Invalid email or password'); + } + + // Connect to the database + await dbConnect(); + + // Check if the user already exists in database + const checkExisting = (await Admins.findOne({ + email, + })) as QueriedAdminData; + + // If the user already exists, return an error + if (checkExisting) { + res.status(422).json({ message: 'Admin email already exists' }); + throw new Error('Admin email already exists'); + } + + // Hash the user's password + const hashedPassword = await hash(password, 12); + + // construct the user object to insert into the database + let adminToInsert: AdminData = { + firstName, + lastName, + email, + password: hashedPassword, + phone: admin.phone, + status: admin.status, + }; + + // Create a new user in the database + const status = await Admins.insertMany(adminToInsert); + + // Return the status of the user creation + res.status(201).json({ message: 'Admin created', ...status }); + } catch (e) { + console.log('Here is the error: ', e); + res.status(500).json({ message: 'An error occurred', error: e }); + } + break; + default: + res.status(400).json({ message: 'Invalid request method' }); + break; + } +} diff --git a/pages/settings.tsx b/pages/settings.tsx index caa6653..0dc6819 100644 --- a/pages/settings.tsx +++ b/pages/settings.tsx @@ -3,7 +3,7 @@ import { signOut } from 'next-auth/react'; import styled from 'styled-components'; import { PageLayout, PageTitle } from '@/styles/table.styles'; import { Admin } from 'mongodb'; -import AdminTable from '@/components/table/AdminTable'; +import AdminTable from '@/components/table/admin-table/AdminTable'; import { Button } from 'antd'; import { LogoutOutlined } from '@ant-design/icons'; diff --git a/utils/next-auth.d.ts b/utils/next-auth.d.ts index 44b8d27..2b0a1aa 100644 --- a/utils/next-auth.d.ts +++ b/utils/next-auth.d.ts @@ -1,3 +1,4 @@ +import { AdminStatus } from 'bookem-shared/src/types/database'; import NextAuth from 'next-auth'; declare module 'next-auth' { @@ -14,6 +15,8 @@ declare module 'next-auth' { image: string; /** The user's name. */ name: string; + /** The user's status. */ + status: AdminStatus; }; } } diff --git a/utils/table-types.ts b/utils/table-types.ts index 5adc05f..b53b4b7 100644 --- a/utils/table-types.ts +++ b/utils/table-types.ts @@ -1,7 +1,9 @@ import { QueriedVolunteerEventDTO, QueriedVolunteerProgramData, + QueriedAdminData, } from 'bookem-shared/src/types/database'; +import { ObjectId } from 'mongodb'; // Event Row export interface EventRowData @@ -21,3 +23,9 @@ export interface ProgramRowData description: string; } export type ProgramDataIndex = keyof ProgramRowData; + +// Admin row +export interface AdminRowData extends QueriedAdminData { + key: string; +} +export type AdminDataIndex = keyof AdminRowData; diff --git a/utils/table-utils.ts b/utils/table-utils.ts index aeaefb4..df33049 100644 --- a/utils/table-utils.ts +++ b/utils/table-utils.ts @@ -1,8 +1,9 @@ import { QueriedVolunteerEventDTO, QueriedVolunteerProgramData, + QueriedAdminData, } from 'bookem-shared/src/types/database'; -import { EventRowData, ProgramRowData } from './table-types'; +import { EventRowData, ProgramRowData, AdminRowData } from './table-types'; export const convertEventDataToRowData = ( data: QueriedVolunteerEventDTO[] @@ -31,3 +32,9 @@ export const convertProgramDataToRowData = ( }); return result; }; + +export const convertAdminDataToRowData = ( + data: QueriedAdminData[] +): AdminRowData[] => { + return data.map(admin => ({ ...admin, key: admin._id.toString() })); +};