Skip to content

Commit 845c555

Browse files
authored
Merge pull request #77 from CS3219-AY2425S1/chore/demo-ui-fixes
(chore): Add live grading UI fixes
2 parents c9c3b14 + 474ec44 commit 845c555

39 files changed

+1089
-126
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE "users" ADD COLUMN "is_admin" boolean DEFAULT false;
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
{
2+
"id": "5293f5bb-f4d5-43a4-b2bf-f6ebb241dde1",
3+
"prevId": "f01e3d91-1038-48b8-a073-a6a9a8a308fd",
4+
"version": "7",
5+
"dialect": "postgresql",
6+
"tables": {
7+
"public.admin": {
8+
"name": "admin",
9+
"schema": "",
10+
"columns": {
11+
"id": {
12+
"name": "id",
13+
"type": "uuid",
14+
"primaryKey": true,
15+
"notNull": true,
16+
"default": "gen_random_uuid()"
17+
},
18+
"created_at": {
19+
"name": "created_at",
20+
"type": "timestamp",
21+
"primaryKey": false,
22+
"notNull": false,
23+
"default": "now()"
24+
},
25+
"action": {
26+
"name": "action",
27+
"type": "action",
28+
"typeSchema": "public",
29+
"primaryKey": false,
30+
"notNull": true
31+
}
32+
},
33+
"indexes": {},
34+
"foreignKeys": {},
35+
"compositePrimaryKeys": {},
36+
"uniqueConstraints": {}
37+
},
38+
"public.users": {
39+
"name": "users",
40+
"schema": "",
41+
"columns": {
42+
"id": {
43+
"name": "id",
44+
"type": "uuid",
45+
"primaryKey": true,
46+
"notNull": true,
47+
"default": "gen_random_uuid()"
48+
},
49+
"email": {
50+
"name": "email",
51+
"type": "varchar(255)",
52+
"primaryKey": false,
53+
"notNull": true
54+
},
55+
"username": {
56+
"name": "username",
57+
"type": "varchar(255)",
58+
"primaryKey": false,
59+
"notNull": true
60+
},
61+
"first_name": {
62+
"name": "first_name",
63+
"type": "varchar(255)",
64+
"primaryKey": false,
65+
"notNull": true
66+
},
67+
"last_name": {
68+
"name": "last_name",
69+
"type": "varchar(255)",
70+
"primaryKey": false,
71+
"notNull": true
72+
},
73+
"password": {
74+
"name": "password",
75+
"type": "varchar(255)",
76+
"primaryKey": false,
77+
"notNull": true
78+
},
79+
"failed_attempts": {
80+
"name": "failed_attempts",
81+
"type": "smallint",
82+
"primaryKey": false,
83+
"notNull": false,
84+
"default": 0
85+
},
86+
"unlock_time": {
87+
"name": "unlock_time",
88+
"type": "timestamp (6) with time zone",
89+
"primaryKey": false,
90+
"notNull": false
91+
},
92+
"attempted_questions": {
93+
"name": "attempted_questions",
94+
"type": "integer[]",
95+
"primaryKey": false,
96+
"notNull": false
97+
},
98+
"is_admin": {
99+
"name": "is_admin",
100+
"type": "boolean",
101+
"primaryKey": false,
102+
"notNull": false,
103+
"default": false
104+
}
105+
},
106+
"indexes": {},
107+
"foreignKeys": {},
108+
"compositePrimaryKeys": {},
109+
"uniqueConstraints": {
110+
"users_email_unique": {
111+
"name": "users_email_unique",
112+
"nullsNotDistinct": false,
113+
"columns": [
114+
"email"
115+
]
116+
},
117+
"users_username_unique": {
118+
"name": "users_username_unique",
119+
"nullsNotDistinct": false,
120+
"columns": [
121+
"username"
122+
]
123+
}
124+
}
125+
}
126+
},
127+
"enums": {
128+
"public.action": {
129+
"name": "action",
130+
"schema": "public",
131+
"values": [
132+
"SEED"
133+
]
134+
}
135+
},
136+
"schemas": {},
137+
"sequences": {},
138+
"_meta": {
139+
"columns": {},
140+
"schemas": {},
141+
"tables": {}
142+
}
143+
}

backend/user/drizzle/meta/_journal.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
"when": 1728143079049,
99
"tag": "0000_initial_schema",
1010
"breakpoints": true
11+
},
12+
{
13+
"idx": 1,
14+
"version": "7",
15+
"when": 1730968227580,
16+
"tag": "0001_add_admin_user",
17+
"breakpoints": true
1118
}
1219
]
1320
}

backend/user/src/controllers/auth-check/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export const checkIsAuthed: IRouteHandler = async (req, res) => {
1616
'[/auth-check/check-is-authed]: Expires At ' + new Date(expireTimeInMillis).toLocaleString()
1717
);
1818
const user = await db
19-
.select({ name: users.username })
19+
.select({ name: users.username, isAdmin: users.isAdmin, email: users.email })
2020
.from(users)
2121
.where(eq(users.id, decoded.id))
2222
.limit(1);
@@ -25,6 +25,8 @@ export const checkIsAuthed: IRouteHandler = async (req, res) => {
2525
expiresAt: expireTimeInMillis,
2626
userId: decoded.id,
2727
username: user.length > 0 ? user[0].name : undefined,
28+
email: user.length > 0 ? user[0].email : undefined,
29+
isAdmin: user.length > 0 ? user[0].isAdmin : undefined,
2830
});
2931
}
3032

backend/user/src/lib/db/schema.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,13 @@
1-
import { integer, pgEnum, pgTable, smallint, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';
1+
import {
2+
boolean,
3+
integer,
4+
pgEnum,
5+
pgTable,
6+
smallint,
7+
timestamp,
8+
uuid,
9+
varchar,
10+
} from 'drizzle-orm/pg-core';
211

312
// Define the user table
413
export const users = pgTable('users', {
@@ -11,6 +20,7 @@ export const users = pgTable('users', {
1120
failedAttempts: smallint('failed_attempts').default(0), // Failed counts
1221
unlockTime: timestamp('unlock_time', { precision: 6, withTimezone: true }), // If failed counts > limit, block all attempts until this time.
1322
attemptedQuestions: integer('attempted_questions').array(),
23+
isAdmin: boolean('is_admin').default(false),
1424
});
1525

1626
export const actionEnum = pgEnum('action', ['SEED']);

backend/user/src/lib/db/seed.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { eq } from 'drizzle-orm';
1+
import { eq, InferInsertModel } from 'drizzle-orm';
22

33
import { generatePasswordHash } from '@/lib/passwords';
44

55
import { admin as adminTable, db, users as usersTable } from '.';
66

7-
const TEST_USER_CREDENTIALS = [
7+
const TEST_USER_CREDENTIALS: Array<InferInsertModel<typeof usersTable>> = [
88
{
99
username: 'testuser01',
1010
@@ -19,6 +19,14 @@ const TEST_USER_CREDENTIALS = [
1919
lastName: 'user02',
2020
password: '123456789', // For local testing purposes
2121
},
22+
{
23+
username: 'adminuser01',
24+
25+
firstName: 'admin',
26+
lastName: 'user01',
27+
password: 'IamPeerprepAdmin!9',
28+
isAdmin: true,
29+
},
2230
];
2331

2432
const main = async () => {

frontend/.env.local

Lines changed: 0 additions & 7 deletions
This file was deleted.

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,6 @@
8282
"tailwindcss": "^3.4.11",
8383
"typescript": "^5.5.3",
8484
"typescript-eslint": "^8.0.1",
85-
"vite": "^5.4.1"
85+
"vite": "^5.4.10"
8686
}
8787
}

frontend/src/components/blocks/authed/with-nav-blocker.tsx

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
import { VisuallyHidden } from '@radix-ui/react-visually-hidden';
12
import { FC, PropsWithChildren } from 'react';
23
import { useBlocker } from 'react-router-dom';
34

45
import { Button } from '@/components/ui/button';
5-
import { Dialog, DialogContent } from '@/components/ui/dialog';
6+
import {
7+
Dialog,
8+
DialogContent,
9+
DialogDescription,
10+
DialogFooter,
11+
DialogTitle,
12+
} from '@/components/ui/dialog';
613

714
export const WithNavBlocker: FC<PropsWithChildren> = ({ children }) => {
815
const blocker = useBlocker(
@@ -13,21 +20,32 @@ export const WithNavBlocker: FC<PropsWithChildren> = ({ children }) => {
1320
{blocker.state === 'blocked' && (
1421
<Dialog modal open>
1522
<DialogContent className='text-primary border-secondary-foreground/40 flex flex-col gap-8'>
16-
<h1 className='text-lg font-medium'>
23+
<DialogTitle className='text-primary text-lg'>
1724
Are you sure you want to navigate away from this page?
18-
</h1>
19-
<div className='flex flex-row justify-between'>
20-
<Button onClick={blocker.reset}>
21-
<span>Cancel</span>
22-
</Button>
23-
<Button variant='destructive' onClick={blocker.proceed}>
24-
<span>Leave Page</span>
25-
</Button>
26-
</div>
27-
<div
28-
id='blockDialogClose'
29-
className='bg-background absolute right-4 top-4 z-50 size-4'
30-
/>
25+
</DialogTitle>
26+
<VisuallyHidden>
27+
<DialogDescription />
28+
</VisuallyHidden>
29+
<DialogFooter>
30+
<div className='flex w-full flex-row justify-between'>
31+
<Button onClick={blocker.reset}>
32+
<span>Cancel</span>
33+
</Button>
34+
<Button
35+
variant='destructive'
36+
onClick={() => {
37+
localStorage.removeItem('ai_chat_history');
38+
blocker.proceed();
39+
}}
40+
>
41+
<span>Leave Page</span>
42+
</Button>
43+
</div>
44+
<div
45+
id='blockDialogClose'
46+
className='bg-background absolute right-4 top-4 z-50 size-4'
47+
/>
48+
</DialogFooter>
3149
</DialogContent>
3250
</Dialog>
3351
)}

frontend/src/components/blocks/interview/chat/chat-layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,15 @@ export const ChatLayout = ({
110110
<Trash2 className='size-4' />
111111
</Button>
112112
</AlertDialogTrigger>
113-
<AlertDialogContent>
113+
<AlertDialogContent className='border-border'>
114114
<AlertDialogHeader>
115-
<AlertDialogTitle>Clear Chat History</AlertDialogTitle>
115+
<AlertDialogTitle className='text-primary'>Clear Chat History</AlertDialogTitle>
116116
<AlertDialogDescription>
117117
Are you sure you want to clear the chat history? This action cannot be undone.
118118
</AlertDialogDescription>
119119
</AlertDialogHeader>
120120
<AlertDialogFooter>
121-
<AlertDialogCancel>Cancel</AlertDialogCancel>
121+
<AlertDialogCancel className='text-primary'>Cancel</AlertDialogCancel>
122122
<AlertDialogAction onClick={onClearHistory}>Clear History</AlertDialogAction>
123123
</AlertDialogFooter>
124124
</AlertDialogContent>

0 commit comments

Comments
 (0)