- Part 1 (Prototype)
- Part 2 (Next.JS)
URL: http://team10.sci-project.lboro.ac.uk/
(submitted)
or http://team10.sci-project.lboro.ac.uk/t10/
(updated)
Top-level PHP files acting as pages (using a custom .htaccess
file to hide file extension)
- A redirect is made in the form of a POST request to the new page, with the user's details as form parameters - redirect/index.ts
- The user's email and role are stored in the attribute
data-user
of thehtml
tag
- Store logged-in user's email in a cookie (1 hour expiry - can change)
- Cookie is automatically sent to server whenever they change page
- If the cookie is not detected, they will be redirected to the login page.
Page URL | Owner | Notes |
---|---|---|
/ |
Dara | Can make / display home instead and if user isn't logged in, redirect to /login ? |
/home |
Michael/Lu | |
/projects |
Michael/Lu | Displays a grid of assigned projects (only on updated version) |
/projects?name= |
Michael/Lu | Displays the kanban board for that project |
/forum |
Ade | |
/dashboard |
David | |
/staff_assignment |
Faye | |
/profile |
Dara | |
/signup |
Dara | Can merge signup and login? |
- jQuery 3.6.1
- Bootstrap 5/5.2/4.1.0
- Popper.js
- Bootstrap Icons 1.9.1
- Font-Awesome (only icons used?) 5.0.13
URL: TODO
Deploy to Vercel for development before using GCP?
https://cloud.google.com/nodejs/getting-started/getting-started-on-compute-engine
Think we also misunderstood the dashboard:
"There should also be a manager’s dashboard so that the managers or team lead‐ ers can keep track of the progression of the project they are responsible for."
The dashboard is for each project, and is more akin to the project overview that we were doing
- Forum
- Redesign
- Organised by topics
- Should be more like Wikipedia
- Projects
- More detailed info about tasks (maybe display a modal containing task info when it is clicked on)
- Deadline
- Estimated time to take? (hours)
- More detailed info about tasks (maybe display a modal containing task info when it is clicked on)
- Dashboard
- Progress display needs to be in terms of time not % of tasks completed
- Searchbar to find project
- Searchbar to find employee
- Create project
- Assign team leader
- More info when adding project
- How many hours for project etc.
- Searchbar for projects in sidebar
- Project name editable (managers/team leaders only)
- Delete project (managers/team leaders only with confirmation dialog)
- Make page for creating a new project instead of using a modal
- Decide what goes in manager dashboard sidebar
- Same as every other page? (list of projects)
- Using NextAuth.js which creates a session (with a JWT storing the user's info)
TODO
- Package Manager: pnpm
- UI: React.Js
- Component Library: MUI Material UI
- Full stack framework (handles routing, SSR, etc.): Next.Js
- Database: MySQL
- Database ORM: Prisma
- HTTP Client: axios
- Containerisation: Docker
- Deployment: GCP
We are using an experimental version of MUI's theming that supports CSS variables. Despite it being "experimental", it has a lot of benefits such as:
- Prevents dark-mode SSR flickering
- Color scheme is automatically synced between browser tabs
The MUI docs explains how using it is different to the standard use of MUI.
Using Docker Compose to run both the app and database together.
This Docker Compose file defines two services:
db
andapp
. Thedb
service is based on the mysql:8.0 image and exposes port 3306. It also defines several environment variables that configure the database. Theapp
service is built from the current directory and exposes port 3000. It also depends on thedb
service, which means that thedb
service will be started before theapp
service. Finally, the file defines a volume calleddb-data
, which is used by thedb
service to store persistent data.ChatGPT explaining docker-compose.prod.yaml
- For pages, you can copy and paste from page_template
- Already done for the pages available in navbar (
home
,forum
, etc.) - For the pages already templated, you can do whatever you want with the actual component
- Already done for the pages available in navbar (
- Use API routes to update database (e.g. for things like adding task)
- Use SSR to get info for page (e.g. getting a user's todo list)
- See example for how to get user during SSR
- Don't make API route for getting data that is gotten during SSR
- Run locally and see all examples at
http://localhost:3000/examples
Your page component needs to be of type AppPage
, e.g.:
import type { AppPage } from '~/types';
const Page: AppPage = () => (
<>
Example
</>
);
export default Page;
To use our defined layout (sidebar and nav in top), you need to add the property layout
to the file's default export:
Prop | Type | Description |
---|---|---|
layout | PageLayout |
Basically Layout 's props |
layout.sidebar | Sidebar |
|
layout.sidebar.type | SidebarType |
|
layout.sidebar.content | ReactNode? (only needed if sidebarType === CUSTOM ) |
AppPage
adds type hints for the above for you
- Most pages will have a
layout.sidebar.type
ofPROJECTS
- e.g. main forum page should be
CUSTOM
- The different forum pages will probably have different sidebar content
- e.g. main forum page should be
If this is unclear, see examples:
There are two ways to know the user that is signed in:
- Reading the
user
prop passed to the page component bygetServerSideProps
(example) - Reading
state.user
from the client-side storestore/userStore
(see next section) (example)
If on a page:
- if anything about the user can change while on your page (e.g. changing name), use number 1.
- else, prefer using number 2 but it's ntd
In a component:
- use number 2 because it won't be able to access
user
fromgetServerSideProps
without passing it as a prop from the page
Example of using useUserStore
(number 2):
import useUserStore from '~/store/userStore';
export default function ExamplePage() {
// this component will re-render whenever `name` changes
const name = useUserStore((state) => state.user.name);
return (
<div>Your name is: {name}</div>
);
}
We are using zustand in a way that is essentially dependency injection - we inject the user in _app.jsx
. We do this
so that we don't repeatedly set the user in each page: instead we just set the user in one place - in _app.jsx
.
- Prettier (formatting) has been set up because it's a waste of time debating code format
- If using VSCode, install the Prettier plugin and you just have to save a file to run the formatter
- See https://prettier.io/docs/en/editors.html if not using VSCode
- A git pre-commit hook has been set up to automatically format changed files using pretty-quick
- ESLint (code style) has been set up
- Make sure your editor/IDE has ESLint support enabled, e.g. VSCode has the ESLint extension
Always use absolute imports. It has been setup in the tsconfig
to use a custom
path ~
with /src
as the baseUrl.
E.g. to import the default export of /src/components/ExampleComponent.ts
from anywhere, you would use
import ExampleComponent from '~/src/components/ExampleComponent';
E.g. to import /public/exampleImage.png
:
import ExampleImage from '~/../public/exampleImage.png';
Imports should be in the order (example):
- External libraries
- react (e.g.
useState
) - NextJs (e.g.
GetServerSidePropsContext
) - NextJs subpackages (e.g.
Head
fromnext/head
) - Next-Auth (e.g.
signIn
fromnext-auth/next
) - React Boostrap/UI library (e.g.
IconButton
from@mui/material/IconButton
) - Any other external libraries (e.g.
axios
)
- react (e.g.
- [space]
- Our code e.g. from
~/types
- [space]
- Other
- External CSS
- Our CSS (e.g.
[componentname].module.css
) - Images
- Anything else
Below isn't really necessary, but is nice, the grouping above is more important
In each of the above groups, imports should be in alphabetic order of the file they're being imported from or logically grouped:
- e.g.
next/head
imports go beforenext/link
- the meaning of "logically grouping" is very loose here, as long as the imports aren't a mess you're gucci
Use dynamic routes instead of URL params, with similar functionality to a REST API
Not sure about the forum pages that aren't yet templated
Will want a page/option to list posts made by a certain user. Maybe on a post, make the author's name clickable, and it'll take u to /forum/authors/[authorId]
Atm there's a link to
/forum/authors
in the forum sidebar
Page URL/Route | Owner | Status | Completed | Notes |
---|---|---|---|---|
/ |
Dara | Complete | Automatically redirects to /home if signed in. |
|
/home |
Michael | Complete | ||
/projects |
Lu | Complete | Display all projects | |
/projects/[id] |
Michael | Complete | A specific project, use components/Task and components/KanbanBoard |
|
/projects/[id]/overview |
Faye | Complete | Manager's overview of a project. Can assign project members, update project leader/title | |
/projects/new |
Faye | Complete | Creating a new project (accessed from manager dashboard) | |
/forum |
Ade | Complete | ... | |
/forum/authors/ |
Sean | Complete | List authors | |
/forum/authors/[id] |
Sean | Complete | Display posts by a specific author | |
/dashboard |
David | Complete | Projects progress | |
/staff |
David | Complete | ||
/profile |
Dara | Complete | ||
/signup |
Lu | Complete |
Use hashids to mask IDs in URLs. Already done in:
projects/[id]/*
/forum/posts/[id]/*
We are using Prisma as an ORM, so we have type safety + don't have to write SQL. Prisma also has nice features such as auto-joins and implicit many-to-many relations.
All have unique IDs (autoincrement
/uuid
)
User
Name | Type | Default | Relation | Description |
---|---|---|---|---|
id | String (UUID) |
uuid() |
||
String |
||||
hashedPassword | String |
|||
name | String |
|||
isAdmin | Boolean |
false |
||
leftCompany | Boolean |
false |
||
inviteToken | String? |
The invite token they used to sign up. | ||
avatarBg | String |
#e2ba39 |
||
avatarFg | String |
#ffffff |
||
todoList | - | UserTask[] |
Implicit many-to-many relation. | |
isManager | Boolean |
false |
||
ledProjects | - | Project[] |
Implicit many-to-many relation. The projects where the user is a team leader. | |
assignedProjects | - | Project[] |
Implicit many-to-many relation. The projects where the user is a team member. | |
permittedTasks | - | ProjectTask[] |
Implicit many-to-many relation. | |
posts | - | Post |
Implicit many-to-many relation. | |
editedPostsHistory | - | PostHistory[] |
Implicit many-to-many relation. | |
upvotedPosts | - | Post |
Implicit many-to-many relation. |
User Task
Name | Type | Default | Relation | Description |
---|---|---|---|---|
id | Int |
autoincrement() |
||
userId | String (UUID) |
User |
||
stage | String |
|||
title | String |
|||
description | String |
|||
deadline | DateTime |
|||
tags | - | UserTaskTag[] |
Implicit many-to-many relation. |
UserTaskTag
is just{ name: string }
Project
Name | Type | Default | Relation | Description |
---|---|---|---|---|
id | Int |
autoincrement() |
||
name | String |
|||
leaderId | String (UUID) |
User |
||
members | - | User[] |
Implicit many-to-many relation. | |
tasks | - | ProjectTask[] |
Project Task
Name | Type | Default | Relation | Description |
---|---|---|---|---|
id | Int |
autoincrement() |
||
projectId | Int |
Project |
||
stage | String |
|||
title | String |
|||
description | String |
|||
deadline | DateTime |
|||
tags | - | ProjectTaskTag[] |
Implicit many-to-many relation. | |
assigneeId | String (UUID) |
User |
||
permitted | - | User[] |
Implicit many-to-many relation. The team member that are permitted to view this task. |
ProjectTaskTag
is just{ name: string }
Post
Name | Type | Default | Relation | Description |
---|---|---|---|---|
id | Int |
autoincrement() |
||
authorId | String (UUID) |
User |
||
topics | - | PostTopic[] |
Implicit many-to-many relation. | |
upvoters | - | User[] |
Implicit many-to-many relation. | |
history | - | PostHistory[] |
PostTopic
is just{ name: string }
PostHistory
Many-to-many relationship table representing User (editor) <-> Post, with additional properties.
Name | Type | Default | Relation | Description |
---|---|---|---|---|
id | Int |
autoincrement() |
||
postId | Int |
Post |
||
editorId | String |
User |
||
date | DateTime |
now() |
||
title | String |
|||
summary | String |
|||
content | String |
TODO: ERM diagram?
- Store a hash of each user's password in database
- User's raw password is not stored in database ✔️
- Hash their passwords using bcrypt ( Article - Library - Wikipedia )
Should we implement functionality to reset password?
But how would it work?
A possible impl.:
- manager gets a link to give to employee (with like a token specifying the employee)
- employee opens that link and resets their password
cons:
- manager could reset the password of any employee they want (could abuse it)
- we should ask client about this?
When running for the first time, run the following commands (in prototype_nextjs
):
npm run migrate:dev
This will create a local SQLite database (prototype_nextjs/prisma/dev.db
), apply our schema to it and
populate it with data
from the
seed.
Name | Minor Version | Purpose |
---|---|---|
TypeScript | 4.9 | Programming language |
ESLint | 8.31 | Static code analysis |
Typescript ESLint | 5.48 | ESLint Typescript support |
Prettier | 2.8 | Code formatting (#56) |
husky | 8.0 | Git hooks |
pretty-quick | 3.1 | Run prettier on changed files as part of pre-commit Git hook |
React | 18.2 | UI library |
Next.Js | 13.1 | Full stack framework |
NextAuth.js | 4.17 | Authentication |
sharp | 0.31 | Next.Js Image Optimization (not used explicitly by us) |
next-superjson-plugin | 0.5 | SuperJSON plugin for Next.Js pages, so we dont have to manually serialie & deserialize dates |
MUI Material UI | 5.11 | Component Library (#40) |
MUI Material UI Lab | 5.0 | MUI Components not yet added to core (e.g. LoadingButton ) |
MUI Material Icons | 5.11 | MUI Icons |
Emotion | 11.10 | Styling engine for MUI, and styled components |
Roboto | 4.5 | MUI default font |
react-hot-toast | 2.4 | Toasts |
Axios | 1.2 | HTTP client (use instead of the fetch API) |
zustand | 4.2 | State management |
Prisma | 4.8 | Database ORM (#12) |
ts-node | 10.9 | Run code to seed Prisma database |
node.bcrypt.js | 5.1 | Hashing user passwords |
Zod | 3.20 | Object schema validation (#1) |
Formik | 2.2 | Form validation (#1) |
formik-validator-zod | 1.0 | Zod adapter for Formik (Formik uses Yup) |
SWR | 4.18 | Client-side data fetching |
hashids | 2.2 | Mask IDs in URLs (#16) |
jsonwebtoken | 9.0 | Generate invite tokens (#19) |
dotenv | 16.0 | Load development environment variables during database seeding |
lodash | 4.17 | Utility library |
lorem-ipsum | 2.0 | Generating placeholder text (for seeding) |
usehooks-ts | 2.9 | Utility React hooks |
type-fest | 3.5 | Utility TypeScript types |
clsx | 1.2 | Utility library for constructing classnames |
remarkable | 2.0 | Rendering markdown content |
... |