Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
a3d2b0e
feat(OUT-2459): add main page for notification center
SandipBajracharya Oct 9, 2025
ca4a7b8
wip(OUT-2459): UI changes for request redirect from notification center
SandipBajracharya Oct 9, 2025
aecf94c
feat(OUT-2459): Notification center view changes in sidebar and notif…
arpandhakal Oct 10, 2025
ffc05a4
feat(OUT-2461): highlight comment card container on notifications
arpandhakal Oct 10, 2025
9ca138c
fix(OUT-2461): highlight animation time on comments
arpandhakal Oct 10, 2025
e9c891b
Merge branch 'feature/client-visibility' of github.com:copilot-platfo…
rrojan Oct 16, 2025
9189e3b
Merge branch 'feature/client-visibility' of https://github.com/copilo…
arpandhakal Oct 28, 2025
3d97e74
Merge branch 'feature/client-visibility' of https://github.com/copilo…
arpandhakal Oct 28, 2025
70bfca5
Merge branch 'feature/client-visibility' of https://github.com/copilo…
arpandhakal Oct 28, 2025
a4a85e6
Merge branch 'feature/client-visibility' of https://github.com/copilo…
arpandhakal Oct 28, 2025
c9a5f68
Merge branch 'feature/client-visibility' of https://github.com/copilo…
arpandhakal Oct 30, 2025
128003d
Merge branch 'feature/client-visibility' of https://github.com/copilo…
arpandhakal Oct 31, 2025
ba199b3
Merge branch 'feature/client-visibility' of https://github.com/copilo…
arpandhakal Nov 4, 2025
c582109
Merge branch 'feature/client-visibility' of https://github.com/copilo…
arpandhakal Nov 5, 2025
5c61347
fix(OUT-2555): disabled changing assignee/ viewer from notification-c…
arpandhakal Nov 5, 2025
21b930b
fix(OUT-2554): redirection to board from notification-center-view on …
arpandhakal Nov 5, 2025
46e989a
fix(OUT-2554): passed fromNotificationCenter to deletedTaskRedirectPage
arpandhakal Nov 5, 2025
187af59
fix(OUT-2554): immage, attachment issue on notification-center-view w…
arpandhakal Nov 5, 2025
96eb1fc
Merge branch 'main' of github.com:copilot-platforms/tasks-app into fe…
rrojan Nov 5, 2025
7fb8b5b
Merge branch 'main' of https://github.com/copilot-platforms/tasks-app…
arpandhakal Nov 5, 2025
8439e00
fix(OUT-2565): disabled subtask navigation from notification-center-v…
arpandhakal Nov 5, 2025
739752c
Merge branch 'main' of https://github.com/copilot-platforms/tasks-app…
arpandhakal Nov 6, 2025
55eaf6e
Merge branch 'main' of https://github.com/copilot-platforms/tasks-app…
arpandhakal Nov 6, 2025
a3e1c3f
Merge branch 'main' of https://github.com/copilot-platforms/tasks-app…
arpandhakal Nov 6, 2025
5f2fb66
fix(OUT-2571): attachment layout on hover on notification-center-view…
arpandhakal Nov 6, 2025
29bee80
feat(OUT-2574): make infra regions reproducable with vercel.json IaC
rrojan Nov 7, 2025
8467e33
fix(OUT-2572): options of delayTouchStart was not being passed to Mod…
arpandhakal Nov 7, 2025
477bcc6
fix(OUT-2572): added a support to detech touch device in dnd wrapper
arpandhakal Nov 7, 2025
134e8db
fix(OUT-2572): removed listening to change events on media query on m…
arpandhakal Nov 10, 2025
fa77909
Merge branch 'main' of github.com:copilot-platforms/tasks-app into fe…
rrojan Nov 10, 2025
03fc3c4
fix(OUT-2694): check for storage access, log and throw error
SandipBajracharya Dec 1, 2025
73946a3
refactor(OUT-2694): log cleanup
SandipBajracharya Dec 2, 2025
29519cd
refactor(OUT-2694): implement proper error handling
SandipBajracharya Dec 3, 2025
ea6b8d1
refactor(OUT-2750): suppress and log error when storage access not gr…
SandipBajracharya Dec 5, 2025
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
29 changes: 27 additions & 2 deletions src/app/_cache/forageStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,35 @@ export async function migrateAssignees(lookupKey: string) {

export async function getAssignees(lookupKey: string): Promise<IAssigneeCombined[]> {
if (typeof window === 'undefined') return []
return (await localforage.getItem<IAssigneeCombined[]>(`assignees.${lookupKey}`)) ?? []

try {
if (!(await document.hasStorageAccess())) {
console.info('Browswer has no storage access')
await document.requestStorageAccess()
}

return (await localforage.getItem<IAssigneeCombined[]>(`assignees.${lookupKey}`)) ?? []
} catch (error: unknown) {
console.error(
"Storage access not granted. Under Chrome's Settings > Privacy and Security, make sure 'Third-party cookies' is allowed.",
)
return []
}
}

export async function setAssignees(lookupKey: string, value: any) {
if (typeof window === 'undefined') return
return await localforage.setItem(`assignees.${lookupKey}`, value)

try {
if (!(await document.hasStorageAccess())) {
console.info('Browswer has no storage access')
await document.requestStorageAccess()
}

return await localforage.setItem(`assignees.${lookupKey}`, value)
} catch (error: unknown) {
console.error(
"Storage access not granted. Under Chrome's Settings > Privacy and Security, make sure 'Third-party cookies' is allowed.",
)
}
}
4 changes: 3 additions & 1 deletion src/app/api/tasks/task-notifications.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,10 @@ export class TaskNotificationsService extends BaseService {
const handleNotificationRead = {
[AssigneeType.client]: this.notificationService.markClientNotificationAsRead,
[AssigneeType.company]: this.notificationService.markAsReadForAllRecipients,
[AssigneeType.internalUser]: (task: Task) =>
this.notificationService.deleteInternalUserNotificationsForTask(task.id),
}
// @ts-expect-error This is completely safe

await handleNotificationRead[task?.assigneeType]?.(task)
}
}
Expand Down
27 changes: 23 additions & 4 deletions src/app/detail/[task_id]/[user_type]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default async function TaskDetailPage({
searchParams,
}: {
params: { task_id: string; task_name: string; user_type: UserType }
searchParams: { token: string; isRedirect?: string }
searchParams: { token: string; isRedirect?: string; fromNotificationCenter?: string }
}) {
const { token } = searchParams
const { task_id, user_type } = params
Expand All @@ -103,10 +103,17 @@ export default async function TaskDetailPage({
if (!tokenPayload) {
throw new Error('Please provide a Valid Token')
}
const fromNotificationCenter = !!searchParams.fromNotificationCenter

console.info(`app/detail/${task_id}/${user_type}/page.tsx | Serving user ${token} with payload`, tokenPayload)
if (!task) {
return <DeletedTaskRedirectPage userType={tokenPayload.companyId ? UserRole.Client : UserRole.IU} token={token} />
return (
<DeletedTaskRedirectPage
userType={tokenPayload.companyId ? UserRole.Client : UserRole.IU}
token={token}
fromNotificationCenter={fromNotificationCenter}
/>
)
}

const isPreviewMode = !!getPreviewMode(tokenPayload)
Expand All @@ -130,7 +137,7 @@ export default async function TaskDetailPage({
{token && <OneTaskDataFetcher token={token} task_id={task_id} initialTask={task} />}
<RealTime tokenPayload={tokenPayload}>
<EscapeHandler />
<ResponsiveStack>
<ResponsiveStack fromNotificationCenter={fromNotificationCenter}>
<Box sx={{ width: '100%', display: 'flex', flex: 1, flexDirection: 'column', overflow: 'hidden' }}>
{isPreviewMode ? (
<StyledBox>
Expand Down Expand Up @@ -192,6 +199,7 @@ export default async function TaskDetailPage({
await deleteAttachment(token, id)
}}
userType={params.user_type}
token={token}
/>
</StyledTiptapDescriptionWrapper>
{subTaskStatus.canCreateSubtask && (
Expand All @@ -207,7 +215,18 @@ export default async function TaskDetailPage({
</TaskDetailsContainer>
</CustomScrollBar>
</Box>
<Box>
<Box
{...(fromNotificationCenter
? {
sx: {
display: 'flex',
overflow: 'hidden',
justifyContent: 'center',
alignItems: 'center',
},
}
: {})}
>
<AssigneeCacheGetter lookupKey={getAssigneeCacheLookupKey(user_type, tokenPayload, isPreviewMode)} />
<AssigneeFetcher
token={token}
Expand Down
1 change: 1 addition & 0 deletions src/app/detail/ui/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const Comments = ({ comment, createComment, deleteComment, task_id, stabl
/>

<CommentCard
data-comment-card="true"
comment={comment}
createComment={createComment}
deleteComment={deleteComment}
Expand Down
24 changes: 20 additions & 4 deletions src/app/detail/ui/ResponsiveStack.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
'use client'

import { selectTaskDetails } from '@/redux/features/taskDetailsSlice'
import { selectTaskDetails, setFromNotificationCenter } from '@/redux/features/taskDetailsSlice'
import store from '@/redux/store'
import { Stack } from '@mui/material'
import { ReactNode } from 'react'
import { ReactNode, useEffect } from 'react'
import { useSelector } from 'react-redux'

export const ResponsiveStack = ({ children }: { children: ReactNode }) => {
export const ResponsiveStack = ({
children,
fromNotificationCenter,
}: {
children: ReactNode
fromNotificationCenter: boolean
}) => {
const { showSidebar } = useSelector(selectTaskDetails)

useEffect(() => {
store.dispatch(setFromNotificationCenter(fromNotificationCenter))
}, [fromNotificationCenter])

return (
<Stack direction={showSidebar ? 'row' : 'column-reverse'} sx={{ height: '100vh' }}>
<Stack
direction={!showSidebar || fromNotificationCenter ? 'column-reverse' : 'row'}
sx={{
height: '100vh',
}}
>
{children}
</Stack>
)
Expand Down
34 changes: 21 additions & 13 deletions src/app/detail/ui/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from '@/utils/assignee'
import { createDateFromFormattedDateString, formatDate } from '@/utils/dateHelper'
import { NoAssignee } from '@/utils/noAssignee'
import { Box, Skeleton, Stack, styled, SxProps, Typography } from '@mui/material'
import {
getSelectedUserIds,
getSelectedViewerIds,
Expand All @@ -42,14 +43,20 @@ import {
shouldConfirmBeforeReassignment,
shouldConfirmViewershipBeforeReassignment,
} from '@/utils/shouldConfirmBeforeReassign'
import { Box, Skeleton, Stack, styled, Typography } from '@mui/material'
import { useEffect, useState } from 'react'
import { useSelector } from 'react-redux'
import { z } from 'zod'

const StyledText = styled(Typography)(({ theme }) => ({
type StyledTypographyProps = {
display?: string
}

const StyledText = styled(Typography, {
shouldForwardProp: (prop: string) => prop !== 'display', // don't pass to DOM
})<StyledTypographyProps>(({ theme, display }) => ({
color: theme.color.gray[500],
width: '80px',
display,
}))

export const Sidebar = ({
Expand All @@ -74,7 +81,7 @@ export const Sidebar = ({
portalUrl?: string
}) => {
const { activeTask, workflowStates, assignee, previewMode } = useSelector(selectTaskBoard)
const { showSidebar, showConfirmAssignModal } = useSelector(selectTaskDetails)
const { showSidebar, showConfirmAssignModal, fromNotificationCenter } = useSelector(selectTaskDetails)
const { tokenPayload } = useSelector(selectAuthDetails)

const [isHydrated, setIsHydrated] = useState(false)
Expand Down Expand Up @@ -196,7 +203,8 @@ export const Sidebar = ({
})
}
}
if (!showSidebar) {

if (!showSidebar || fromNotificationCenter) {
return (
<Stack
direction="row"
Expand All @@ -209,7 +217,7 @@ export const Sidebar = ({
maxWidth: '654px',
justifyContent: 'flex-start',
alignItems: 'center',
width: '100%',
width: fromNotificationCenter ? '654px' : 'auto',
margin: '0 auto',
display: 'flex',
}}
Expand Down Expand Up @@ -261,11 +269,11 @@ export const Sidebar = ({
<CopilotPopSelector
name="Set assignee"
onChange={handleAssigneeChange}
disabled={disabled}
disabled={disabled || fromNotificationCenter}
initialValue={assigneeValue}
buttonContent={
<SelectorButton
disabled={disabled}
disabled={disabled || fromNotificationCenter}
height={'30px'}
startIcon={<CopilotAvatar currentAssignee={assigneeValue} />}
buttonContent={
Expand Down Expand Up @@ -301,11 +309,11 @@ export const Sidebar = ({
hideIusList
name="Set client visibility"
onChange={handleTaskViewerChange}
disabled={disabled && !previewMode}
disabled={(disabled && !previewMode) || fromNotificationCenter}
initialValue={taskViewerValue || undefined}
buttonContent={
<SelectorButton
disabled={disabled && !previewMode}
disabled={(disabled && !previewMode) || fromNotificationCenter}
height={'30px'}
startIcon={<CopilotAvatar currentAssignee={taskViewerValue || undefined} />}
buttonContent={
Expand Down Expand Up @@ -474,11 +482,11 @@ export const Sidebar = ({
<CopilotPopSelector
name="Set assignee"
onChange={handleAssigneeChange}
disabled={disabled}
disabled={disabled || fromNotificationCenter}
initialValue={assigneeValue}
buttonContent={
<SelectorButton
disabled={disabled}
disabled={disabled || fromNotificationCenter}
padding="0px"
startIcon={<CopilotAvatar width="16px" height="16px" currentAssignee={assigneeValue} />}
outlined={true}
Expand Down Expand Up @@ -529,11 +537,11 @@ export const Sidebar = ({
hideIusList
name="Set client visibility"
onChange={handleTaskViewerChange}
disabled={disabled && !previewMode} // allow visibility change in preview mode
disabled={(disabled && !previewMode) || fromNotificationCenter} // allow visibility change in preview mode
initialValue={taskViewerValue || undefined}
buttonContent={
<SelectorButton
disabled={disabled && !previewMode}
disabled={(disabled && !previewMode) || fromNotificationCenter}
padding="0px"
startIcon={<CopilotAvatar width="16px" height="16px" currentAssignee={taskViewerValue || undefined} />}
outlined={true}
Expand Down
3 changes: 3 additions & 0 deletions src/app/detail/ui/Subtasks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { GrayAddMediumIcon } from '@/icons'
import { selectAuthDetails } from '@/redux/features/authDetailsSlice'
import { selectTaskBoard } from '@/redux/features/taskBoardSlice'
import { selectTaskDetails } from '@/redux/features/taskDetailsSlice'
import { CreateTaskRequest, TaskResponse } from '@/types/dto/tasks.dto'
import { fetcher } from '@/utils/fetcher'
import { generateRandomString } from '@/utils/generateRandomString'
Expand Down Expand Up @@ -40,6 +41,7 @@
const [openTaskForm, setOpenTaskForm] = useState(false)
const { workflowStates, assignee, activeTask } = useSelector(selectTaskBoard)
const { tokenPayload } = useSelector(selectAuthDetails)
const { fromNotificationCenter } = useSelector(selectTaskDetails)
const [optimisticUpdates, setOptimisticUpdates] = useState<OptimisticUpdate[]>([]) //might need this server-temp id maps in the future.
const [lastUpdated, setLastUpdated] = useState<string | null>()
const handleFormCancel = () => setOpenTaskForm(false)
Expand Down Expand Up @@ -73,7 +75,7 @@
debounceMutate(cacheKey)
}
setLastUpdated(activeTask?.lastSubtaskUpdated)
}, [activeTask?.lastSubtaskUpdated])

Check warning on line 78 in src/app/detail/ui/Subtasks.tsx

View workflow job for this annotation

GitHub Actions / Run linters

React Hook useEffect has missing dependencies: 'activeTask', 'cacheKey', 'debounceMutate', and 'lastUpdated'. Either include them or remove the dependency array

const handleSubTaskCreation = (payload: CreateTaskRequest) => {
const tempId = generateRandomString('temp-task')
Expand Down Expand Up @@ -200,6 +202,7 @@
variant="subtask"
mode={mode}
handleUpdate={handleSubTaskUpdate}
disableNavigation={fromNotificationCenter}
/>
)
})}
Expand Down
25 changes: 20 additions & 5 deletions src/app/detail/ui/TaskCardList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,19 @@ interface TaskCardListProps {
handleUpdate?: (taskId: string, changes: Partial<TaskResponse>, updater: () => Promise<void>) => Promise<void>
isTemp?: boolean
sx?: SxProps<Theme> | undefined
disableNavigation?: boolean
}

export const TaskCardList = ({ task, variant, workflowState, mode, handleUpdate, isTemp, sx }: TaskCardListProps) => {
export const TaskCardList = ({
task,
variant,
workflowState,
mode,
handleUpdate,
isTemp,
sx,
disableNavigation = false,
}: TaskCardListProps) => {
const { assignee, workflowStates, previewMode, token, confirmAssignModalId, assigneeCache, confirmViewershipModalId } =
useSelector(selectTaskBoard)
const { tokenPayload } = useSelector(selectAuthDetails)
Expand Down Expand Up @@ -173,8 +183,13 @@ export const TaskCardList = ({ task, variant, workflowState, mode, handleUpdate,
justifyContent: 'flex-end',
minWidth: 0,
':hover': {
cursor: 'pointer',
background: (theme) => (variant == 'subtask-board' ? theme.color.gray[150] : theme.color.gray[100]),
cursor: disableNavigation ? 'auto' : 'pointer',
background: (theme) =>
disableNavigation
? theme.color.base.white
: variant == 'subtask-board'
? theme.color.gray[150]
: theme.color.gray[100],
},
':focus-visible': {
outline: (theme) => `1px solid ${theme.color.borders.focusBorder2}`,
Expand Down Expand Up @@ -220,7 +235,7 @@ export const TaskCardList = ({ task, variant, workflowState, mode, handleUpdate,
disabled={checkIfTaskViewer(task.viewers, tokenPayload)}
/>

{isTemp || variant === 'subtask-board' ? (
{isTemp || variant === 'subtask-board' || disableNavigation ? (
<div
key={task.id}
style={{
Expand Down Expand Up @@ -279,7 +294,7 @@ export const TaskCardList = ({ task, variant, workflowState, mode, handleUpdate,
flexShrink: 1,
}}
>
<TaskTitle variant="list" title={task.title} />
<TaskTitle variant={variant == 'task' ? 'list' : 'subtasks'} title={task.title} />
{(task.subtaskCount > 0 || task.isArchived) && (
<Stack direction="row" sx={{ display: 'flex', gap: '12px', flexShrink: 0, alignItems: 'center' }}>
<TaskMetaItems task={task} lineHeight="21px" />
Expand Down
4 changes: 3 additions & 1 deletion src/app/detail/ui/TaskEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
postAttachment: (postAttachmentPayload: CreateAttachmentRequest) => void
deleteAttachment: (id: string) => void
userType: UserType
token: string
}

export const TaskEditor = ({
Expand All @@ -44,11 +45,12 @@
postAttachment,
deleteAttachment,
userType,
token,
}: Prop) => {
const [updateTitle, setUpdateTitle] = useState('')
const [updateDetail, setUpdateDetail] = useState('')
const { showConfirmDeleteModal, openImage } = useSelector(selectTaskDetails)
const { token, activeTask } = useSelector(selectTaskBoard)
const { activeTask } = useSelector(selectTaskBoard)
const [isUserTyping, setIsUserTyping] = useState(false)
const [activeUploads, setActiveUploads] = useState(0)

Expand Down Expand Up @@ -80,7 +82,7 @@
setUpdateDetail(currentTask.body ?? '')
}
}
}, [activeTask?.title, activeTask?.body, task_id, activeUploads, task])

Check warning on line 85 in src/app/detail/ui/TaskEditor.tsx

View workflow job for this annotation

GitHub Actions / Run linters

React Hook useEffect has missing dependencies: 'activeTask' and 'isUserTyping'. Either include them or remove the dependency array

const _titleUpdateDebounced = async (title: string) => updateTaskTitle(title)

Expand Down
4 changes: 2 additions & 2 deletions src/app/detail/ui/ToggleController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ReactNode, useEffect } from 'react'
import { useSelector } from 'react-redux'

export const ToggleController = ({ children }: { children: ReactNode }) => {
const { showSidebar } = useSelector(selectTaskDetails)
const { showSidebar, fromNotificationCenter } = useSelector(selectTaskDetails)
const matches = useMediaQuery('(max-width:800px)')

const nonMobile = useMediaQuery('(min-width:800px)')
Expand All @@ -21,7 +21,7 @@ export const ToggleController = ({ children }: { children: ReactNode }) => {
return (
<Box
sx={{
width: showSidebar ? 'calc(100% - 339px)' : '100%',
width: showSidebar && !fromNotificationCenter ? 'calc(100% - 339px)' : '100%',
display: matches && showSidebar ? 'none' : 'flex',
flex: 1,
flexDirection: 'column',
Expand Down
Loading
Loading