Skip to content

Commit 3a4f152

Browse files
committed
feat: implement review submission and update functionality with TRPC integration
1 parent 777fda8 commit 3a4f152

5 files changed

Lines changed: 97 additions & 138 deletions

File tree

src/app/(admin)/admin/api/proposals/[id]/review/route.ts

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

src/components/admin/ProposalReviewForm.tsx

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import { useState } from 'react'
44
import { StarIcon } from '@heroicons/react/24/solid'
55
import { PaperAirplaneIcon, ArrowRightIcon } from '@heroicons/react/24/outline'
6-
import { Review, ReviewBase } from '@/lib/review/types'
7-
import { adminPostReview } from '@/lib/review/client'
6+
import { Review } from '@/lib/review/types'
87
import { adminFetchNextUnreviewedProposal } from '@/lib/proposal'
8+
import { api } from '@/lib/trpc/client'
99
import { useNotification } from './NotificationProvider'
1010
import { Textarea } from '@/components/Form'
1111
import { AdminButton } from '@/components/admin/AdminButton'
@@ -26,6 +26,7 @@ export function ProposalReviewForm({
2626
}: ProposalReviewFormProps) {
2727
const router = useRouter()
2828
const { showNotification } = useNotification()
29+
const utils = api.useUtils()
2930
const [ratings, setRatings] = useState<{
3031
content: number
3132
relevance: number
@@ -44,24 +45,24 @@ export function ProposalReviewForm({
4445
const [isSubmitting, setIsSubmitting] = useState<boolean>(false)
4546
const [isLoadingNext, setIsLoadingNext] = useState<boolean>(false)
4647

48+
const submitReviewMutation = api.proposal.admin.submitReview.useMutation({
49+
onSuccess: (review) => {
50+
utils.proposal.admin.getById.invalidate({ id: proposalId })
51+
utils.proposal.admin.list.invalidate()
52+
onReviewSubmit(review as Review)
53+
},
54+
})
55+
4756
const submitHandler = async (e: React.FormEvent) => {
4857
e.preventDefault()
4958
setIsSubmitting(true)
5059

51-
const reviewData: ReviewBase = {
52-
comment,
53-
score: ratings,
54-
}
55-
5660
try {
57-
const res = await adminPostReview(proposalId, reviewData)
58-
59-
if (res.reviewError || !res.review) {
60-
console.error('Error submitting review:', res.reviewError)
61-
return
62-
}
63-
64-
onReviewSubmit(res.review)
61+
await submitReviewMutation.mutateAsync({
62+
id: proposalId,
63+
comment,
64+
score: ratings,
65+
})
6566
} catch (error) {
6667
console.error('Error submitting review:', error)
6768
} finally {

src/lib/review/client.ts

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

src/server/routers/proposal.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
IdParamSchema,
2121
ProposalActionSchema,
2222
AudienceFeedbackSchema,
23+
SubmitReviewSchema,
2324
} from '@/server/schemas/proposal'
2425
import { AttachmentSchema } from '@/server/schemas/attachment'
2526
import {
@@ -47,6 +48,7 @@ import { Speaker } from '@/lib/speaker/types'
4748
import { eventBus } from '@/lib/events/bus'
4849
import { ProposalStatusChangeEvent } from '@/lib/events/types'
4950
import { updateProposalStatus, getProposalSanity } from '@/lib/proposal/server'
51+
import { createReview, updateReview } from '@/lib/review/sanity'
5052
import '@/lib/events/registry'
5153

5254
/**
@@ -332,7 +334,7 @@ export const proposalRouter = router({
332334
if (!ctx.speaker.isOrganizer && existing.conference) {
333335
const conferenceId =
334336
typeof existing.conference === 'object' &&
335-
'_id' in existing.conference
337+
'_id' in existing.conference
336338
? existing.conference._id
337339
: typeof existing.conference === 'string'
338340
? existing.conference
@@ -863,6 +865,72 @@ export const proposalRouter = router({
863865
})
864866
}
865867
}),
868+
869+
// Submit or update a review (admin)
870+
submitReview: adminProcedure
871+
.input(SubmitReviewSchema)
872+
.mutation(async ({ input, ctx }) => {
873+
try {
874+
const { proposal, proposalError } = await getProposal({
875+
id: input.id,
876+
speakerId: ctx.speaker._id,
877+
isOrganizer: true,
878+
includeReviews: true,
879+
})
880+
881+
if (proposalError || !proposal || !proposal._id) {
882+
throw new TRPCError({
883+
code: proposalError ? 'INTERNAL_SERVER_ERROR' : 'NOT_FOUND',
884+
message: proposalError
885+
? 'Failed to fetch proposal'
886+
: 'Proposal not found',
887+
cause: proposalError,
888+
})
889+
}
890+
891+
const existingReview = proposal.reviews?.find(
892+
(r) => 'email' in r.reviewer && r.reviewer._id === ctx.speaker._id,
893+
)
894+
895+
const conferenceId =
896+
'_id' in proposal.conference
897+
? proposal.conference._id
898+
: (proposal.conference as { _ref: string })._ref
899+
900+
const reviewData = { comment: input.comment, score: input.score }
901+
902+
const { review, reviewError } = existingReview
903+
? await updateReview(
904+
existingReview._id,
905+
ctx.speaker._id,
906+
reviewData,
907+
)
908+
: await createReview(
909+
proposal._id,
910+
ctx.speaker._id,
911+
conferenceId,
912+
reviewData,
913+
)
914+
915+
if (reviewError || !review) {
916+
throw new TRPCError({
917+
code: 'INTERNAL_SERVER_ERROR',
918+
message: `Failed to ${existingReview ? 'update' : 'create'} review`,
919+
cause: reviewError,
920+
})
921+
}
922+
923+
return review
924+
} catch (error) {
925+
if (error instanceof TRPCError) throw error
926+
927+
throw new TRPCError({
928+
code: 'INTERNAL_SERVER_ERROR',
929+
message: 'Failed to submit review',
930+
cause: error,
931+
})
932+
}
933+
}),
866934
}),
867935

868936
// Speaker attachment operations

src/server/schemas/proposal.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,4 +170,15 @@ export const AudienceFeedbackSchema = z.object({
170170
redCount: z.number().int().min(0),
171171
})
172172

173-
export const IdParamSchema = CommonIdParamSchema
173+
export const ReviewScoreSchema = z.object({
174+
content: z.number().int().min(1).max(5),
175+
relevance: z.number().int().min(1).max(5),
176+
speaker: z.number().int().min(1).max(5),
177+
})
178+
179+
export const SubmitReviewSchema = CommonIdParamSchema.extend({
180+
comment: z.string(),
181+
score: ReviewScoreSchema,
182+
})
183+
184+
export { CommonIdParamSchema as IdParamSchema }

0 commit comments

Comments
 (0)