Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Commit

Permalink
Merge pull request #49 from quayside-app/usability
Browse files Browse the repository at this point in the history
Usability
  • Loading branch information
AKashton authored Nov 24, 2023
2 parents 0a2fa42 + b8d41a7 commit dd2fae9
Show file tree
Hide file tree
Showing 13 changed files with 131 additions and 39 deletions.
4 changes: 4 additions & 0 deletions public/svg/load.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/svg/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 32 additions & 2 deletions src/app/[...projectIds]/page.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
'use client'
import TreeGraph from '../../components/Graph'
import Button from '../../components/Button'
import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'

/**
* Renders a page component that displays the tree graph with a dynamic route
Expand All @@ -11,12 +14,39 @@ import Button from '../../components/Button'
* the project IDs and a TreeGraph component for the project.
*/
export default function page ({ params }) {
const [project, setProject] = useState(null)
const router = useRouter()
async function deleteProject (projectID, router) {
await fetch(`/api/mongoDB/deleteProject?projectID=${projectID}`, {
method: 'DELETE'
}).catch(error => console.error('Error:', error))
router.push('/')
}

useEffect(() => {
// Fetch project data
fetch(`/api/mongoDB/getProjects?projectID=${params.projectIds}`, {
method: 'GET'
}).then(async (response) => {
const body = await response.json()
if (!response.ok) {
console.error(body.message)
}
setProject(body.projects)
}).catch(error => {
console.error('Error querying project data:', error)
})
}, [])

return (
<div className='p-4 text-xl flex w-full flex-wrap'>

<div className='flex w-full'>
<div className='flex w-11/12'> Project: {params.projectIds} </div>
<div className='flex w-1/12 justify-end'><Button label='Delete Project' /></div>
<div className='flex w-10/12 flex-wrap'> Project: {project && project.name} </div>
<div className='flex w-2/12 justify-end'>
{/* On click, delete project, return to home page, and refresh */}
<Button label='Delete Project' clickAction={() => { deleteProject(params.projectIds, router) }} className='bg-red-800 ' isCentered='true' />
</div>
</div>

<TreeGraph projectID={params.projectIds} className='flex w-full' />
Expand Down
4 changes: 2 additions & 2 deletions src/app/api/mongoDB/createProject/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { createTask } from '../createTask/createTask'
*
*
* @param {Object} request - The request object containing the project details.
* @returns {Object} - A response object with a status code and a message.
* @returns {Object} - A response object with a status code, a message, and the created Project Document.
*
* @example
* fetch(`/api/mongoDB/createProject`, {
Expand Down Expand Up @@ -137,7 +137,7 @@ export async function POST (request) {
parseTask(task, rootId, projectId)
}

return NextResponse.json({ message: 'Project and tasks created successfully' }, { status: 200 })
return NextResponse.json({ message: 'Project and tasks created successfully', project: projectDocument }, { status: 200 })
} catch (error) {
return NextResponse.json({ message: 'Error creating projects and tasks:' + error }, { status: 500 })
}
Expand Down
45 changes: 45 additions & 0 deletions src/app/api/mongoDB/deleteProject/route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { options } from '../../auth/[...nextauth]/options'
import { getServerSession } from 'next-auth/next'

import { NextResponse } from 'next/server'
import mongoose from 'mongoose'
import { Project, Task } from '../mongoModels'
import { URI } from '../mongoData.js'

/**
* Handles a DELETE request to delete the project and all tasks of the specified project ID.
* Returns an error message if the user is not verified.
*
* @param {Request} request - The incoming request object, containing the URL projctID parameter.
* @param {string} [projectID] - The unique ID of the project to retrieve.
* @returns {NextResponse} A Next.js response object. If successful, it returns a 200 status with a success
* message. In case of an error, it returns a 500 status with an error message.
*
* @returns {Object} - A response object with a status code and the confirmation or error message.
*
* @example
* // Example usage:
* await fetch(`/api/mongoDB/deleteProject?projectID=1234`, {
* method: 'DELETE'
* }).catch(error => console.error('Error:', error));
*/

export async function DELETE (request) {
try {
const session = await getServerSession(options)
if (!session) {
return NextResponse.json({ success: false, message: 'authentication failed' }, { status: 401 })
}

const params = await request.nextUrl.searchParams
const projectID = params.get('projectID')

if (mongoose.connection.readyState !== 1) await mongoose.connect(URI)
await Task.deleteMany({ projectID })
await Project.deleteOne({ _id: projectID })

return NextResponse.json({ message: 'Project deleted successfully' }, { status: 200 })
} catch (error) {
return NextResponse.json({ message: 'Error deleting project ' + error }, { status: 500 })
}
}
13 changes: 10 additions & 3 deletions src/app/api/mongoDB/getProjects/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { URI } from '../mongoData.js'
* }).catch(error => console.error(error));
*
* @property {string} request.nextUrl.searchParams.userID - The user ID whose projects are to be retrieved.
* @property {string} request.nextUrl.searchParams.projectID - The project ID of the project to be retrieved
*/
export async function GET (request) {
try {
Expand All @@ -39,15 +40,21 @@ export async function GET (request) {
}
const params = await request.nextUrl.searchParams
const userID = params.get('userID')
const projectID = params.get('projectID')

// Require userID
if (!userID) {
return NextResponse.json({ message: 'User ID required.' }, { status: 400 })
if (!userID && !projectID) {
return NextResponse.json({ message: 'User ID or Project ID required.' }, { status: 400 })
}

if (mongoose.connection.readyState !== 1) await mongoose.connect(URI)

const projects = await Project.find({ userIDs: userID })
let projects
if (userID) {
projects = await Project.find({ userIDs: userID })
} else {
projects = await Project.findOne({ _id: projectID })
}
return NextResponse.json({ projects }, { status: 200 })
} catch (error) {
return NextResponse.json({ message: 'Error getting data ' + error }, { status: 500 })
Expand Down
4 changes: 2 additions & 2 deletions src/app/layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ async function RootLayout ({ children }) {
<NewProjectModal />
<Navbar />
<div className='flex h-screen'>
<LeftSidebar className='flex w-1/3 md:w-60' />
<div className='flex w-2/3 md:w-max ml-5'> {children} </div>
<LeftSidebar className='w-96 lg:w-56' />
<div className='flex w-max ml-5'> {children} </div>
</div>
</div>
</Provider>
Expand Down
4 changes: 2 additions & 2 deletions src/app/page.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export default function Home () {
return (
<main className='flex flex-wrap w-full flex-col items-center'>
<div className='flex w-full flex-wrap items-center'>
<main className='flex flex-wrap w-full'>
<div className='flex w-full flex-wrap '>
Create or Select a project.
</div>
</main>
Expand Down
17 changes: 8 additions & 9 deletions src/components/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@
import React from 'react'
import Image from 'next/image'

const Button = ({ label, clickAction, imagePath, className }) => {
const Button = ({ label, clickAction, imagePath, isCentered, className }) => {
return (
<div className={className}>
<button type='button' onClick={clickAction} className='flex items-center w-full p-2 text-base text-white transition duration-75 rounded-lg group hover:bg-gray-700' aria-controls='dropdown-example' data-collapse-toggle='dropdown-example'>
<div className='flex w-full'>
{imagePath && <Image priority src={imagePath} alt={label} height='20' width='20' className='mr-3' />}
<span className='flex my-auto whitespace-nowrap text-xs xl:text-lg'>{label}</span>
</div>
</button>
</div>

<button type='button' onClick={clickAction} className={`flex p-1 text-base text-white transition duration-75 rounded-lg group hover:bg-gray-700 ${className}`} aria-controls='dropdown-example' data-collapse-toggle='dropdown-example'>
<div className={`flex w-full ${isCentered && 'justify-center'}`}>
{imagePath && <Image priority src={imagePath} alt={label} height='20' width='20' className='mr-3' />}
<span className='flex my-auto truncate'>{label}</span>
</div>
</button>

)
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/Dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ const Dropdown = ({ label, clickAction, imagePath }) => {
<div className='flex w-full'>
<div className='flex w-11/12'>
<Image priority src={imagePath} alt={label} height='20' width='20' />
<span className='flex my-auto ml-3 text-left whitespace-nowrap text-xs xl:text-lg'>{label}</span>
<span className='flex my-auto ml-3 text-left whitespace-nowrap'>{label}</span>
</div>
<div className='flex 1-1/12 justify-end'>
<div className='flex 1-1/12 justify-end '>
<Image priority src={dropdownIcon} alt='Dropdown Icon' height='20' width='20' />
</div>
</div>
Expand Down
4 changes: 0 additions & 4 deletions src/components/Graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ function TreeGraph ({ className, projectID }) {
const elements = []

tasks.forEach(task => {
console.log(task._id)
console.log(task.parentTaskID)
// Assume task has id, name, and parent fields
elements.push({
data: { id: task._id, label: task.name }
Expand All @@ -57,8 +55,6 @@ function TreeGraph ({ className, projectID }) {
}
})

// console.log(elements);

// Tailwind's bg-gray-200 #E5E7EB
cytoscape({

Expand Down
15 changes: 8 additions & 7 deletions src/components/LeftSidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React, { useEffect, useState } from 'react'
import Link from 'next/link'
import { useSession } from 'next-auth/react'
import { usePathname } from 'next/navigation'

import NewProjectButton from '../components/NewProjectButton'
import ContactUsButton from '../components/ContactUsButton'
Expand Down Expand Up @@ -35,6 +36,7 @@ import targetIcon from '../../public/svg/target.svg'
export default function LeftSidebar ({ className }) {
const [projectsDiv, setProjectsDiv] = useState(<div />)
const { data: session } = useSession()
const pathname = usePathname()

useEffect(() => {
// Fetch project data
Expand All @@ -49,8 +51,8 @@ export default function LeftSidebar ({ className }) {
<div>
<ul>
{body.projects.map((project, index) => (
<li key={index} className=' font-light text-sm'>
<Link href={`/${project._id}`}><Button label={project.name} /></Link>
<li key={index} className=' font-light '>
<Link href={`/${project._id}`}><Button label={project.name} className='w-32' /></Link>
</li>
))}
</ul>
Expand All @@ -60,15 +62,14 @@ export default function LeftSidebar ({ className }) {
}).catch(error => {
console.error('Left sidebar Project warning:', error)
})
}, []) // Empty dependency array prevents bajilliion api calls
}, [pathname]) // Changes when path changes or on load

return (
<div className={className}>
<div className='flex flex-wrap bg-neutral-800 text-white justify-center py-5 h-full w-full'>
<div className='flex w-full flex-wrap bg-neutral-800'>

<div className='mx-4 w-full'>
<div className='flex w-full bg-neutral-800 px-4 pt-10 min-h-screen text-white justify-center'>
<ul className='font-medium'>
<li> <span className='ml-3 flex justify-center py-5'>Directory</span> </li>
<NewProjectButton />
<li> <Button label='Task' imagePath={plusIcon} /> </li>

Expand All @@ -82,7 +83,7 @@ export default function LeftSidebar ({ className }) {
</ul>
</div>

<div className='mt-auto sticky bottom-0 bg-neutral-700 w-full'>
<div className='mt-auto sticky bottom-0 bg-neutral-700 w-full '>
<ContactUsButton />
</div>
</div>
Expand Down
21 changes: 16 additions & 5 deletions src/components/NewProjectModal.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { useSession } from 'next-auth/react'
import Input from '../components/Input'
import Alert from '../components/Alert'
import xIcon from '../../public/svg/x.svg'
import loadIcon from '../../public/svg/load.svg'

import Image from 'next/image'
import { useRouter } from 'next/navigation'

/**
* A modal component for creating a new project. It provides a form to collect details about the project such as the ChatGPT API Key, project description, completion date, budget, and stakeholders.
Expand All @@ -26,8 +28,10 @@ import Image from 'next/image'
* <NewProjectModal isOpen={isModalOpen} handleClose={() => setIsModalOpen(false)} />
*/
const NewProjectModal = ({ isOpen, handleClose }) => {
const router = useRouter()
const [errorMessage, setMessage] = useState(null)
const { data: session } = useSession()
const [isLoading, setLoading] = useState(false)
const [formData, setFormData] = useState({
prompt: '',
question1: '',
Expand All @@ -48,6 +52,7 @@ const NewProjectModal = ({ isOpen, handleClose }) => {

async function submitForm (e) {
e.preventDefault() // Prevents page from refreshing
setLoading(true)

// Send data to DB
try {
Expand All @@ -69,11 +74,13 @@ const NewProjectModal = ({ isOpen, handleClose }) => {
setMessage(body.message)
return
}
const projectID = body.project._id
setLoading(false)
handleClose()
router.push(`/${projectID}`) // Routes to the document just created
} catch (error) {
console.error('Error setting new project.')
return
console.error('Error creating new project.')
}
handleClose()
}

if (!isOpen) return null
Expand All @@ -95,8 +102,12 @@ const NewProjectModal = ({ isOpen, handleClose }) => {
<Input name='question2' value={formData.question2} changeAction={handleInput} label='What is the budget allocated for this project?' placeholder='One Billion Dollars and 1 cent' />
<Input name='question3' value={formData.question3} changeAction={handleInput} label='Who are the key stakeholders involved in this project?' placeholder='my boss' />

<button type='submit' className='w-full text-white bg-gray-700 hover:bg-blue-800 focus:ring-4 font-medium rounded-lg text-sm px-5 py-2.5 text-center'>
Create
{/* Disable the button and show loading symbol while isLoading */}
<button type='submit' disabled={isLoading} className={`w-full text-white bg-gray-700 ${!isLoading && 'hover:bg-blue-800'} focus:ring-4 font-medium rounded-lg text-md px-5 py-2.5 text-center`}>
{isLoading
? <div className='flex flex-wrap items-center justify-center'> <Image src={loadIcon} alt='loading' width='25' height='25' className='animate-spin' /> <div className='text-md font-medium mx-3 '>Loading...</div></div>
: 'Create'}

</button>
</form>
</div>
Expand Down

0 comments on commit dd2fae9

Please sign in to comment.