Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions packages/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2230,6 +2230,25 @@ export enum AlertType {
REPHRASE_QUESTION = 'rephraseQuestion',
EVENT_ENDED_CHECKOUT_STAFF = 'eventEndedCheckoutStaff',
PROMPT_STUDENT_TO_LEAVE_QUEUE = 'promptStudentToLeaveQueue',
DOCUMENT_PROCESSED = 'documentProcessed',
ASYNC_QUESTION_UPDATE = 'asyncQuestionUpdate',
}

// Allowed combinations to enforce front/back consistency
export const FEED_ALERT_TYPES: AlertType[] = [
AlertType.DOCUMENT_PROCESSED,
AlertType.ASYNC_QUESTION_UPDATE,
]

export const MODAL_ALERT_TYPES: AlertType[] = [
AlertType.REPHRASE_QUESTION,
AlertType.EVENT_ENDED_CHECKOUT_STAFF,
AlertType.PROMPT_STUDENT_TO_LEAVE_QUEUE,
]

export enum AlertDeliveryMode {
MODAL = 'modal',
FEED = 'feed',
}

export class AlertPayload {}
Expand All @@ -2238,12 +2257,19 @@ export class Alert {
@IsEnum(AlertType)
alertType!: AlertType

@IsEnum(AlertDeliveryMode)
deliveryMode!: AlertDeliveryMode

@IsDate()
sent!: Date

@Type(() => AlertPayload)
payload!: AlertPayload

@IsOptional()
@IsDate()
readAt?: Date

@IsInt()
id!: number
}
Expand All @@ -2266,6 +2292,35 @@ export class PromptStudentToLeaveQueuePayload extends AlertPayload {
queueQuestionId?: number
}

export class DocumentProcessedPayload extends AlertPayload {
@IsInt()
documentId!: number

@IsString()
documentName!: string
}

export class AsyncQuestionUpdatePayload extends AlertPayload {
@IsInt()
questionId!: number

@IsInt()
courseId!: number

@IsString()
@IsOptional()
subtype?:
| 'commentOnMyPost'
| 'commentOnOthersPost'
| 'humanAnswered'
| 'statusChanged'
| 'upvoted'
Comment on lines +2310 to +2317
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this (and all references to it) should 100% be put into an enum


@IsString()
@IsOptional()
summary?: string
}

export class OrganizationCourseResponse {
@IsInt()
id?: number
Expand Down Expand Up @@ -2296,6 +2351,10 @@ export class CreateAlertParams {
@IsEnum(AlertType)
alertType!: AlertType

@IsOptional()
@IsEnum(AlertDeliveryMode)
deliveryMode?: AlertDeliveryMode

@IsInt()
courseId!: number

Expand All @@ -2311,6 +2370,9 @@ export class CreateAlertResponse extends Alert {}
export class GetAlertsResponse {
@Type(() => Alert)
alerts!: Alert[]
@IsOptional()
@IsInt()
total?: number
}

// not used anywhere
Expand Down
23 changes: 22 additions & 1 deletion packages/frontend/app/(dashboard)/course/[cid]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
'use client'
import { use } from 'react'
import { use, useEffect } from 'react'
import useSWR from 'swr'

import AlertsContainer from '@/app/components/AlertsContainer'
import { useAlertsContext } from '@/app/contexts/alertsContext'
import { API } from '@/app/api'

type Params = Promise<{ cid: string }>

Expand All @@ -15,6 +18,24 @@ export default function Layout(props: {

const { cid } = params

const { setThisCourseAlerts, clearThisCourseAlerts } = useAlertsContext()

const { data } = useSWR(
`/api/v1/alerts/course/${cid}`,
async () => API.alerts.get(Number(cid)),
{ refreshInterval: 60000 },
)

useEffect(() => {
setThisCourseAlerts(data?.alerts ?? [])
}, [data?.alerts])

useEffect(() => {
return () => {
clearThisCourseAlerts()
}
}, [])

return (
<>
<AlertsContainer courseId={Number(cid)} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
UndoOutlined,
} from '@ant-design/icons'
import {
AlertDeliveryMode,
AlertType,
ClosedQuestionStatus,
ERROR_MESSAGES,
Expand Down Expand Up @@ -159,6 +160,7 @@ const TAQuestionCardButtons: React.FC<TAQuestionCardButtonsProps> = ({
courseId,
payload,
targetUserId: question.creatorId,
deliveryMode: AlertDeliveryMode.FEED,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
deliveryMode: AlertDeliveryMode.FEED,
deliveryMode: AlertDeliveryMode.MODAL,

})
// await mutateQuestions()
message.success('Successfully asked student to rephrase their question.')
Expand Down
65 changes: 34 additions & 31 deletions packages/frontend/app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import ChatbotContextProvider from './course/[cid]/components/chatbot/ChatbotPro
import FooterBar from './components/FooterBar'
import { AsyncToasterProvider } from '../contexts/AsyncToasterContext'
import { LogoutOutlined, ReloadOutlined } from '@ant-design/icons'
import { AlertsProvider } from '../contexts/alertsContext'
import { getErrorMessage } from '../utils/generalUtils'

const Layout: React.FC<LayoutProps> = ({ children }) => {
Expand Down Expand Up @@ -96,37 +97,39 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
) : (
<AsyncToasterProvider>
<UserInfoProvider profile={profile}>
<header className={`border-b border-b-zinc-200 bg-white`}>
<StandardPageContainer className="!pl-0">
<Link href={'#skip-link-target'} className="skip-link">
Skip to main content
</Link>
<HeaderBar />
</StandardPageContainer>
</header>
{/* This flex flex-grow is needed so that the scroll bar doesn't show up on every page */}
<main className="flex flex-grow flex-col">
<ChatbotContextProvider>
{pathname === '/courses' && (
<Image
src={`/api/v1/organization/${profile.organization.orgId}/get_banner/${profile.organization.organizationBannerUrl}`}
alt="Organization Banner"
className="h-[15vh] w-full object-cover object-center md:h-[20vh]"
width={100}
height={100}
/>
)}
{/* On certain pages (like pages with big tables), we want to let the width take up the whole page */}
{URLSegments[4] === 'edit_questions' ||
URLSegments[4] === 'chatbot_questions' ? (
<div className="p-1">{children}</div>
) : (
<StandardPageContainer className="flex-grow">
{children}
</StandardPageContainer>
)}
</ChatbotContextProvider>
</main>
<AlertsProvider>
<header className={`border-b border-b-zinc-200 bg-white`}>
<StandardPageContainer className="!pl-0">
<Link href={'#skip-link-target'} className="skip-link">
Skip to main content
</Link>
<HeaderBar />
</StandardPageContainer>
</header>
{/* This flex flex-grow is needed so that the scroll bar doesn't show up on every page */}
<main className="flex flex-grow flex-col">
<ChatbotContextProvider>
{pathname === '/courses' && (
<Image
src={`/api/v1/organization/${profile.organization.orgId}/get_banner/${profile.organization.organizationBannerUrl}`}
alt="Organization Banner"
className="h-[15vh] w-full object-cover object-center md:h-[20vh]"
width={100}
height={100}
/>
)}
{/* On certain pages (like pages with big tables), we want to let the width take up the whole page */}
{URLSegments[4] === 'edit_questions' ||
URLSegments[4] === 'chatbot_questions' ? (
<div className="p-1">{children}</div>
) : (
<StandardPageContainer className="flex-grow">
{children}
</StandardPageContainer>
)}
</ChatbotContextProvider>
</main>
</AlertsProvider>
<FooterBar />
</UserInfoProvider>
</AsyncToasterProvider>
Expand Down
19 changes: 19 additions & 0 deletions packages/frontend/app/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
DesktopNotifPartial,
EditCourseInfoParams,
GetAlertsResponse,
AlertDeliveryMode,
GetAvailableModelsBody,
GetChatbotHistoryResponse,
GetCourseResponse,
Expand Down Expand Up @@ -1078,12 +1079,30 @@ class APIClient {
}

alerts = {
markReadAll: async (): Promise<void> =>
this.req('PATCH', `/api/v1/alerts/mark-read-all`),
get: async (courseId: number): Promise<GetAlertsResponse> =>
this.req('GET', `/api/v1/alerts/${courseId}`),
getAll: async (
mode: AlertDeliveryMode = AlertDeliveryMode.FEED,
includeRead: boolean = true,
limit?: number,
offset?: number,
): Promise<GetAlertsResponse> =>
this.req('GET', `/api/v1/alerts`, undefined, undefined, {
mode,
includeRead: includeRead ? 'true' : 'false',
...(limit !== undefined ? { limit: String(limit) } : {}),
...(offset !== undefined ? { offset: String(offset) } : {}),
}),
create: async (params: CreateAlertParams): Promise<CreateAlertResponse> =>
this.req('POST', `/api/v1/alerts`, CreateAlertResponse, params),
close: async (alertId: number): Promise<void> =>
this.req('PATCH', `/api/v1/alerts/${alertId}`),
markReadBulk: async (alertIds: number[]): Promise<void> =>
this.req('PATCH', `/api/v1/alerts/mark-read-bulk`, undefined, {
alertIds,
}),
}

organizations = {
Expand Down
22 changes: 6 additions & 16 deletions packages/frontend/app/components/AlertsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import {
PromptStudentToLeaveQueuePayload,
RephraseQuestionPayload,
} from '@koh/common'
import useSWR from 'swr'
import { useRouter } from 'next/navigation'
import StudentRephraseModal from '../(dashboard)/course/[cid]/queue/[qid]/components/modals/StudentRephraseModal'
import { API } from '../api'
import { useAlertsContext } from '@/app/contexts/alertsContext'
import EventEndedCheckoutStaffModal from '../(dashboard)/course/[cid]/queue/[qid]/components/modals/EventEndedCheckoutStaffModal'
import PromptStudentToLeaveQueueModal from '../(dashboard)/course/[cid]/queue/[qid]/components/modals/PromptStudentToLeaveQueueModal'

Expand All @@ -15,23 +15,15 @@ type AlertsContainerProps = {
}
const AlertsContainer: React.FC<AlertsContainerProps> = ({ courseId }) => {
const router = useRouter()
const { data, mutate: mutateAlerts } = useSWR(
'/api/v1/alerts',
async () => API.alerts.get(courseId),
{
refreshInterval: 60000, // revalidate every minute
},
)
const alerts = data?.alerts
const { thisCourseAlerts, closeCourseAlert } = useAlertsContext()
const alerts = thisCourseAlerts

const handleCloseRephrase = async (
alertId: number,
courseId: number,
queueId: number,
) => {
await API.alerts.close(alertId)

await mutateAlerts()
await closeCourseAlert(alertId)
router.push(`/course/${courseId}/queue/${queueId}?edit_question=true`)
}

Expand All @@ -51,8 +43,7 @@ const AlertsContainer: React.FC<AlertsContainerProps> = ({ courseId }) => {
<EventEndedCheckoutStaffModal
courseId={courseId}
handleClose={async () => {
await API.alerts.close(alert.id)
await mutateAlerts()
await closeCourseAlert(alert.id)
}}
/>
)
Expand All @@ -67,8 +58,7 @@ const AlertsContainer: React.FC<AlertsContainerProps> = ({ courseId }) => {
.queueQuestionId
}
handleClose={async () => {
await API.alerts.close(alert.id)
await mutateAlerts()
await closeCourseAlert(alert.id)
}}
/>
)
Expand Down
9 changes: 9 additions & 0 deletions packages/frontend/app/components/HeaderBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { Popconfirm } from 'antd'
import { sortQueues } from '../(dashboard)/course/[cid]/utils/commonCourseFunctions'
import { useCourseFeatures } from '../hooks/useCourseFeatures'
import CenteredSpinner from './CenteredSpinner'
import NotificationBell from './NotificationBell'
import Image from 'next/image'
import { useOrganizationSettings } from '@/app/hooks/useOrganizationSettings'

Expand Down Expand Up @@ -355,6 +356,11 @@ const NavBar = ({
) : null}
{/* DESKTOP ONLY PART OF NAVBAR */}
<NavigationMenuItem className="!ml-auto hidden md:block">
<div className="px-2">
<NotificationBell />
</div>
</NavigationMenuItem>
<NavigationMenuItem className="hidden md:block">
<NavigationMenuTrigger
className={`!pl-4 ${isProfilePage ? 'md:border-helpmeblue md:border-b-2' : ''}`}
onFocus={setNavigationSubMenuRightSide}
Expand Down Expand Up @@ -509,6 +515,9 @@ const HeaderBar: React.FC = () => {
</h2>
)}
</div>
<div className="flex items-center">
<NotificationBell />
</div>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the bell on mobile should be moved since it makes the page title/subtitle off-centre (also, for long course/queue names or on phones with increased text size, it may cause the title text to overflow/wrap easier which looks really bad)
Image

Proposed solution:

Place an antd Badge on the hamburger menu itself
Image

Place the notification bell somewhere in this drawer (though ngl I'm having some trouble thinking of an area to place it. Can't place it at the top since the university name can be longer. Can't place it beside the user's name since their name can be longer)
Image


one option

Perhaps we can place it in-between the profile and logout buttons, similar to the desktop solution I proposed)
Image

But once the user clicks on it... I'm not sure what should happen, all the options I can think of are kinda gross:

  • Have a submenu (similar to the Queues dropdown). However, it can't actually drop downwards due to the lack of space beneath so the menu, so it would need to drop "upwards" which feels kinda jank. Also it'd be really easy to accidentally close this notifications submenu by accidentally tapping, thus requiring you to re-open the hamburger menu and then re-open the notifications menu. Overall though, this isn't a terrible option i think
  • Open a modal (kinda yucky imo, i think it would have higher cognitive load, but it could at least be re-used if the desktop also uses a modal for notifications)

another option

Just say frick it and fit the notification bell up top where the university name is.

Image

This might get kinda iffy for future orgs, but at least now you can make a proper dropdown submenu.

It is also a little more hidden up there imo so people might have a little more trouble finding it (at least in OC's case where the OC logo has a similar colour to the red "1" for notifications), but overall I think this is the better option.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i agree with hiding it in the mobile sidebar menu thing though bc of the lack of usable space on mobile

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is still not resolved

Image

While it doesn't look that terrible, I have a few issues with it:

  • The bell and hamburger menu are very close together, meaning it's gonna be easy to accidentally tap either one.
  • It makes the course title off-centre (small)
  • Lack of space

So I think it should be moved into the navigation menu beside the organization logo, as stated in "another option" above. Also don't forget to place the Badge on the hamburger icon too

<Drawer
direction="left"
open={isDrawerOpen}
Expand Down
Loading