Skip to content

Commit

Permalink
Merge branch 'master' into 3917-question-search-remix
Browse files Browse the repository at this point in the history
  • Loading branch information
benfurber authored Dec 11, 2024
2 parents 3a977cf + 11b098d commit 2b7b2aa
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 55 deletions.
1 change: 1 addition & 0 deletions src/models/comment.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export class Comment {
sourceType: string
parentId: number | null
deleted: boolean | null
highlighted?: boolean
replies?: Reply[]

constructor(obj: Comment) {
Expand Down
52 changes: 36 additions & 16 deletions src/pages/common/CommentsV2/CommentItemV2.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createRef, useEffect, useMemo, useState } from 'react'
import { createRef, useEffect, useMemo, useRef, useState } from 'react'
import { compareDesc } from 'date-fns'
import { observer } from 'mobx-react'
import {
Expand Down Expand Up @@ -43,11 +43,14 @@ export const CommentItemV2 = observer(
onDeleteReply,
}: ICommentItemProps) => {
const textRef = createRef<any>()
const commentRef = useRef<HTMLDivElement>()
const [showEditModal, setShowEditModal] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [textHeight, setTextHeight] = useState(0)
const [isShowMore, setShowMore] = useState(false)
const [showReplies, setShowReplies] = useState(false)
const [showReplies, setShowReplies] = useState(
() => !!comment.replies?.some((x) => x.highlighted),
)
const { userStore } = useCommonStores().stores

const maxHeight = isShowMore ? 'max-content' : '128px'
Expand All @@ -71,24 +74,43 @@ export const CommentItemV2 = observer(
}, [textRef])

const showMore = () => {
setShowMore(!isShowMore)
setShowMore((prev) => !prev)
}

useEffect(() => {
if (comment.highlighted) {
commentRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
})
}
}, [comment.highlighted])

return (
<Flex
id={`comment:${comment.id}`}
data-cy={isEditable ? `Own${item}` : item}
sx={{ flexDirection: 'column' }}
>
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
{comment.deleted && (
<Box sx={{ marginBottom: 2 }} data-cy="deletedComment">
<Flex sx={{ gap: 2, flexDirection: 'column' }} ref={commentRef as any}>
{comment.deleted ? (
<Box
sx={{
marginBottom: 2,
border: `${comment.highlighted ? '2px dashed black' : 'none'}`,
}}
data-cy="deletedComment"
>
<Text sx={{ color: 'grey' }}>[{DELETED_COMMENT}]</Text>
</Box>
)}

{!comment.deleted && (
<Flex sx={{ gap: 2, flexGrow: 1 }}>
) : (
<Flex
sx={{
gap: 2,
flexGrow: 1,
border: `${comment.highlighted ? '2px dashed black' : 'none'}`,
}}
>
<Box data-cy="commentAvatar" data-testid="commentAvatar">
<CommentAvatar
name={comment.createdBy?.name}
Expand Down Expand Up @@ -219,12 +241,10 @@ export const CommentItemV2 = observer(
/>
))}

{!comment.deleted && (
<CreateCommentV2
onSubmit={(comment) => onReply(comment)}
buttonLabel="Leave a reply"
/>
)}
<CreateCommentV2
onSubmit={(comment) => onReply(comment)}
buttonLabel="Leave a reply"
/>
</>
)}
<ButtonShowReplies
Expand Down
36 changes: 28 additions & 8 deletions src/pages/common/CommentsV2/CommentReply.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createRef, useEffect, useMemo, useState } from 'react'
import { createRef, useEffect, useMemo, useRef, useState } from 'react'
import { compareDesc } from 'date-fns'
import { observer } from 'mobx-react'
import {
Expand Down Expand Up @@ -30,6 +30,7 @@ export interface ICommentItemProps {
export const CommentReply = observer(
({ comment, onEdit, onDelete }: ICommentItemProps) => {
const textRef = createRef<any>()
const commentRef = useRef<HTMLDivElement>()
const [showEditModal, setShowEditModal] = useState(false)
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [textHeight, setTextHeight] = useState(0)
Expand Down Expand Up @@ -60,6 +61,15 @@ export const CommentReply = observer(
setShowMore(!isShowMore)
}

useEffect(() => {
if (comment.highlighted) {
commentRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'center',
})
}
}, [comment.highlighted])

return (
<Flex>
<Box
Expand All @@ -75,15 +85,25 @@ export const CommentReply = observer(
data-cy={isEditable ? `Own${item}` : item}
sx={{ flexDirection: 'column', width: '100%' }}
>
<Flex sx={{ gap: 2 }}>
{comment.deleted && (
<Box sx={{ marginBottom: 2 }} data-cy="deletedComment">
<Flex sx={{ gap: 2 }} ref={commentRef as any}>
{comment.deleted ? (
<Box
sx={{
marginBottom: 2,
border: `${comment.highlighted ? '2px dashed black' : 'none'}`,
}}
data-cy="deletedComment"
>
<Text sx={{ color: 'grey' }}>[{DELETED_COMMENT}]</Text>
</Box>
)}

{!comment.deleted && (
<Flex sx={{ gap: 2, flexGrow: 1 }}>
) : (
<Flex
sx={{
gap: 2,
flexGrow: 1,
border: `${comment.highlighted ? '2px dashed black' : 'none'}`,
}}
>
<Box data-cy="commentAvatar" data-testid="commentAvatar">
<CommentAvatar
name={comment.createdBy?.name}
Expand Down
32 changes: 31 additions & 1 deletion src/pages/common/CommentsV2/CommentSectionV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,37 @@ const CommentSectionV2 = ({ sourceId }: CommentsV2Props) => {
const fetchComments = async () => {
try {
const result = await fetch(`/api/discussions/${sourceId}/comments`)
const { comments } = await result.json()
const { comments } = (await result.json()) as { comments: Comment[] }

const highlightedCommentId = location.hash.replace('#comment:', '')

if (highlightedCommentId) {
const highlightedComment = comments.find(
(x) => x.id === +highlightedCommentId,
)
if (highlightedComment) {
highlightedComment.highlighted = true
} else {
// find in replies and set highlighted
const highlightedReply = comments
.flatMap((x) => x.replies)
.find((x) => x?.id === +highlightedCommentId)

if (highlightedReply) {
highlightedReply.highlighted = true
}
}

// ensure highlighted comment is visible
const index = comments.findIndex(
(x) => x.highlighted || x.replies?.some((y) => y.highlighted),
)

if (index > 5) {
setCommentLimit(index + 1)
}
}

setComments(comments || [])
} catch (err) {
console.error(err)
Expand Down
96 changes: 66 additions & 30 deletions src/routes/api.discussions.$sourceId.comments.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { json, type LoaderFunctionArgs } from '@remix-run/node'
import { verifyFirebaseToken } from 'src/firestore/firestoreAdmin.server'
import { Comment, DBComment } from 'src/models/comment.model'
import { createSupabaseServerClient } from 'src/repository/supabase.server'
import { notificationsService } from 'src/services/notificationsService.server'

import type { LoaderFunctionArgs } from '@remix-run/node'
import type { Params } from '@remix-run/react'
import type { DBCommentAuthor, Reply } from 'src/models/comment.model'
import type { DBProfile } from 'src/models/profile.model'

export async function loader({ params, request }: LoaderFunctionArgs) {
if (!params.sourceId) {
return json({}, { status: 400, statusText: 'sourceId is required' })
return Response.json(
{},
{ status: 400, statusText: 'sourceId is required' },
)
}

const { client, headers } = createSupabaseServerClient(request)
Expand Down Expand Up @@ -40,7 +44,7 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
if (result.error) {
console.error(result.error)

return json({}, { headers, status: 500 })
return Response.json({}, { headers, status: 500 })
}

const dbComments = result.data.map(
Expand All @@ -67,55 +71,53 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
)
return Comment.fromDB(
mainComment,
replies.sort((a, b) => a.id - b.id),
replies.filter((x) => !x.deleted).sort((a, b) => a.id - b.id),
)
},
)

return json({ comments: commentWithReplies }, { headers })
// remove deleted comments that don't have replies
const deletedFilter = commentWithReplies.filter(
(comment: Comment) =>
!comment.deleted || (comment.replies?.length || 0) > 0,
)

return Response.json({ comments: deletedFilter }, { headers })
}

export async function action({ params, request }: LoaderFunctionArgs) {
const { valid, user_id } = await verifyFirebaseToken(
const tokenValidation = await verifyFirebaseToken(
request.headers.get('firebaseToken')!,
)

if (!valid) {
return json({}, { status: 401, statusText: 'unauthorized' })
}

if (!user_id) {
return json({}, { status: 400, statusText: 'user not found' })
}

if (!params.sourceId) {
return json({}, { status: 400, statusText: 'sourceId is required' })
}

if (request.method !== 'POST') {
return json({}, { status: 405, statusText: 'method not allowed' })
}

const userId = tokenValidation.user_id
const data = await request.json()

if (!data.comment) {
return json({}, { status: 400, statusText: 'comment is required' })
}
const { valid, status, statusText } = await validateRequest(
params,
request,
tokenValidation.valid,
userId,
data,
)

if (!data.sourceType) {
return json({}, { status: 400, statusText: 'sourceType is required' })
if (!valid) {
return Response.json({}, { status, statusText })
}

const { client, headers } = createSupabaseServerClient(request)

const currentUser = await client
.from('profiles')
.select()
.eq('firebase_auth_id', user_id)
.eq('firebase_auth_id', userId)
.single()

if (currentUser.error || !currentUser.data) {
return json({}, { status: 400, statusText: 'profile not found ' + user_id })
return Response.json(
{},
{ status: 400, statusText: 'profile not found ' + userId },
)
}

const newComment = {
Expand Down Expand Up @@ -158,7 +160,7 @@ export async function action({ params, request }: LoaderFunctionArgs) {
)
}

return json(
return Response.json(
new DBComment({
...(commentResult.data as DBComment),
profile: (commentResult.data as any).profiles as DBCommentAuthor,
Expand All @@ -169,3 +171,37 @@ export async function action({ params, request }: LoaderFunctionArgs) {
},
)
}

async function validateRequest(
params: Params<string>,
request: Request,
isTokenValid: boolean,
userId: string,
data: any,
) {
if (!isTokenValid) {
return { status: 401, statusText: 'unauthorized' }
}

if (!userId) {
return { status: 400, statusText: 'user not found' }
}

if (!params.sourceId) {
return { status: 400, statusText: 'sourceId is required' }
}

if (request.method !== 'POST') {
return { status: 405, statusText: 'method not allowed' }
}

if (!data.comment) {
return { status: 400, statusText: 'comment is required' }
}

if (!data.sourceType) {
return { status: 400, statusText: 'sourceType is required' }
}

return { valid: true }
}

0 comments on commit 2b7b2aa

Please sign in to comment.