[CCI-4] 일대다 면접 리스트 조회, 상세 조회, 면접 신청 기능 추가 및 번호로 로그인하기 기능 추가#3
[CCI-4] 일대다 면접 리스트 조회, 상세 조회, 면접 신청 기능 추가 및 번호로 로그인하기 기능 추가#3
Conversation
WalkthroughThis update introduces new API endpoints and TypeScript interfaces for group interview management, including updating interview options, retrieving group interview cards and details, and starting interviews. Several React components are refactored to fetch and display live data from these new APIs, replacing static mock data and enhancing user interaction and state management. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI
participant API
participant Server
User->>UI: Navigates to group interview page
UI->>API: getGroupInterviewCards()
API->>Server: Fetch group interview cards
Server-->>API: Returns list of group interviews
API-->>UI: Returns data
UI->>User: Displays interview cards
User->>UI: Selects interview for details
UI->>API: getGroupInterviewDetail(interviewId)
API->>Server: Fetch interview detail
Server-->>API: Returns interview detail
API-->>UI: Returns data
UI->>User: Displays interview detail
User->>UI: Joins interview (selects resume/cover letter)
UI->>API: updateInterviewOption(interviewId, memberId, data)
API->>Server: Update interview option
Server-->>API: Returns update status
API-->>UI: Returns response
UI->>User: Shows confirmation
Possibly related PRs
Suggested labels
Poem
✨ Finishing Touches
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 1
🔭 Outside diff range comments (1)
app/workspace/interview/group/community/[id]/page.tsx (1)
3-20:⚠️ Potential issueAvoid experimental
use()in a client component & simplifyparamshandling
use()is still experimental and, in Next .js 14, is only available in server components.
Because this file is marked"use client", hydration will fail in browsers that do not bundle the experimental hook.
You can safely treatparamssynchronously – it is never aPromiseon the client.-import { useState, useEffect } from "react" +import { useState, useEffect } from "react" … -import { use as usePromise } from "react" … -export default function InterviewPostDetailPage({ params }: { params: Promise<{ id: string }> }) { - const { id: postId } = usePromise(params) +export default function InterviewPostDetailPage({ params }: { params: { id: string } }) { + const { id: postId } = paramsThis removes a hard runtime dependency on an unstable API and aligns with the official Next .js route‐handler signature.
🧹 Nitpick comments (6)
app/login/page.tsx (1)
44-54: Consider improving the temporary login UX.While the current
window.prompt()implementation works for testing purposes, consider these improvements for better user experience:
- Replace
window.prompt()with a proper modal dialog component- Add better input validation feedback
- Consider persisting the login state properly
The current validation logic is solid and handles edge cases well.
Here's a potential improvement using a proper dialog:
- <Button variant="outline" className="w-full" onClick={() => { - const input = window.prompt('로그인할 멤버 ID를 입력하세요') - if (!input) return - const id = Number(input) - if (isNaN(id) || id <= 0) { - alert('올바른 숫자 ID를 입력하세요') - return - } - login(id) - window.location.href = "/workspace" - }}> + <Button variant="outline" className="w-full" onClick={handleTempLogin}> 임시 로그인 </Button>And add a proper dialog component for better UX.
api/types/interview-types.ts (1)
236-236: Address the TODO comment for better type safety.The comment indicates that
coverLetterDTOneeds proper typing instead ofany. This should be addressed to maintain type safety throughout the application.Consider creating a proper interface for the cover letter DTO:
export interface CoverLetterSimpleDTO { coverLetterId?: number; title?: string; content?: string; // Add other relevant fields based on your cover letter data structure }Then update the ParticipantDTO:
export interface ParticipantDTO { memberInterviewId?: number; resumeDTO?: ResumeSimpleDTO; - coverLetterDTO?: any; // CoverletterDetailDTO 등 실제 타입에 맞게 수정 필요 + coverLetterDTO?: CoverLetterSimpleDTO; }app/workspace/interview/group/community/[id]/page.tsx (2)
86-105: Hard-coded “추천 모집글” mock should be moved to live data or feature flagThe component now relies on live data everywhere except
recommendedPosts, which is still static.
Mixing mock and production data can confuse users once the backend goes live. Either:
- Replace this with an API call (preferred), or
- Hide this block behind a feature flag until the endpoint exists.
No action required if this is intentional for early demos.
106-114: Surfacing raw error messages may leak implementation details
error.messagecan contain internal server traces. Consider showing a generic message and logging the detailed error only in non-production builds.-let msg = '데이터 없음' +let msg = '데이터를 불러오지 못했습니다.' … -else if (error instanceof Error) msg = error.message +else if (process.env.NODE_ENV !== 'production' && error instanceof Error) { + msg = error.message +}app/workspace/interview/group/community/[id]/join/page.tsx (2)
49-53:isAlreadyAppliedmay evaluate before data is readyWhen
interviewLoadingistrue,postisundefined, sosome()will throw.
Although?.protects the call, the expression still runs each render.
Move the computation into auseMemothat depends onpostto avoid unnecessary evaluations:const isAlreadyApplied = useMemo( () => post?.groupInterviewParticipants?.some(p => p.memberId === memberId) ?? false, [post, memberId] )
55-72: API error surfaces internal message & misses type-guard
onErrorassumeserrhas.message. Guard explicitly and avoid leaking internal details in production:-onError: (err: any) => { - alert(err?.message || '면접 참여 신청에 실패했습니다.') +onError: (err) => { + const msg = + err instanceof Error && process.env.NODE_ENV !== 'production' + ? err.message + : '면접 참여 신청에 실패했습니다.' + alert(msg) },
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
api/interview.ts(2 hunks)api/types/interview-types.ts(1 hunks)app/login/page.tsx(1 hunks)app/workspace/interview/group/community/[id]/join/page.tsx(7 hunks)app/workspace/interview/group/community/[id]/page.tsx(8 hunks)app/workspace/interview/group/community/page.tsx(4 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
api/interview.ts (2)
api/types/interview-types.ts (5)
InterviewOptionUpdateDTO(109-121)ApiResponseInterviewOptionUpdateResponseDTO(123-128)ApiResponseListInterviewGroupCardDTO(147-152)ApiResponseGroupInterviewDetailDTO(166-171)ApiResponseInterviewStartResponseDTO(195-200)utils/fetch/fetch.ts (1)
serverFetch(135-135)
app/workspace/interview/group/community/[id]/join/page.tsx (7)
components/member-session-context.tsx (1)
useMemberSession(39-43)api/interview.ts (2)
getGroupInterviewDetail(57-61)createMemberInterview(27-35)lib/api.ts (1)
post(1-15)api/resume.ts (1)
getResumeList(33-35)api/coverletter.ts (1)
findMyCoverletter(25-29)components/ui/select.tsx (1)
SelectItem(156-156)components/ui/button.tsx (1)
Button(56-56)
🔇 Additional comments (14)
api/interview.ts (3)
11-16: Well-structured imports for new functionality.The new TypeScript interface imports are properly organized and support the new API functions effectively.
37-47: Excellent API function implementation.The
updateInterviewOptionfunction follows established patterns with:
- Proper TypeScript generics for type safety
- Appropriate PATCH method for updates
- Correct parameter structure including memberId
- Consistent error handling
49-68: Clean and consistent API functions for group interview features.The three new GET endpoints (
getGroupInterviewCards,getGroupInterviewDetail,startInterview) are well-implemented with:
- Proper TypeScript typing
- RESTful URL patterns
- Consistent return types
- Clear function names that match their purpose
app/workspace/interview/group/community/page.tsx (6)
13-14: Proper API integration with React Query.The imports for the new API function and React Query are correctly added to support the data-driven refactoring.
17-23: Excellent React Query implementation.The query setup is well-structured with:
- Descriptive query key
- Proper API function integration
- Destructured response for easy access to loading and error states
30-46: Well-updated filtering logic for new data structure.The filtering logic has been properly adapted to work with the new API data structure:
- Date filtering uses
startedAtfield appropriately- Type filtering maps correctly to
interviewTypeenum values- Search functionality covers both
nameandjobNamefields- Safe navigation with optional chaining
75-84: Robust date formatting with proper null handling.The
formatDatefunction properly handles undefined/null values and provides consistent formatting for the Korean locale.
167-171: Proper loading and error state handling.The UI appropriately displays loading states and error messages, providing good user feedback during API calls.
207-212: Improved button implementation with proper Link component.The button correctly uses the
asChildprop with nested Link component for better semantic HTML and routing behavior.api/types/interview-types.ts (3)
109-144: Well-structured interview option types.The
InterviewOptionUpdateDTOand related response types are properly defined with:
- Consistent voice type enums
- Appropriate field types
- Proper optional field markings for response DTOs
147-192: Comprehensive group interview type definitions.The group interview interfaces are well-designed with:
- Clear separation between list and detail views
- Proper participant data structure
- Consistent field naming and types
- Appropriate use of optional fields
195-237: Complete interview start response types.The interview start functionality is properly typed with comprehensive DTOs covering all necessary data structures.
app/workspace/interview/group/community/[id]/page.tsx (1)
28-31:isHostlogic compares different identifiers
memberId(numeric) is being compared topost.hostName(string).
Unless the backend sends the host’s member id inhostName, this check will always befalse, preventing hosts from accessing host-only controls.Please compare against an id field (
hostId,hostMemberId, …) returned by the API:-const isHost = !!( - memberId && post && post.hostName && String(memberId) === post.hostName -) +const isHost = memberId !== undefined && post?.hostMemberId === memberIdIf the DTO does not yet expose such an id, consider updating it.
app/workspace/interview/group/community/[id]/join/page.tsx (1)
74-86: Host check useshostNameinstead of host idSame mismatch as in the detail page; this could wrongly block/allow applications.
-if (memberId && post.hostName && String(memberId) === post.hostName) { +if (memberId !== undefined && post?.hostMemberId === memberId) {Verify the correct identifier is available in the DTO.
| import { useState, use as usePromise } from "react" | ||
| import Link from "next/link" | ||
| import { Button } from "@/components/ui/button" | ||
| import { ArrowLeft, ArrowRight, Info } from "lucide-react" | ||
| import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" | ||
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" | ||
| import { Label } from "@/components/ui/label" | ||
| import { useQuery, useMutation } from '@tanstack/react-query' | ||
| import { getGroupInterviewDetail } from '@/api/interview' | ||
| import { getResumeList } from '@/api/resume' | ||
| import { findMyCoverletter } from '@/api/coverletter' | ||
| import { createMemberInterview } from '@/api/interview' | ||
| import { useMemberSession } from '@/components/member-session-context' | ||
|
|
||
| export default function JoinGroupInterviewPage({ params }: { params: { id: string } }) { | ||
| const postId = params.id | ||
| export default function JoinGroupInterviewPage({ params }: { params: Promise<{ id: string }> }) { | ||
| const { id: postId } = usePromise(params) | ||
| const { memberId } = useMemberSession() |
There was a problem hiding this comment.
Remove Promise-based params & experimental use() here as well
Same reasoning as in the detail page: this is a client component and receives params synchronously.
-import { useState, use as usePromise } from "react"
+import { useState } from "react"
…
-export default function JoinGroupInterviewPage({ params }: { params: Promise<{ id: string }> }) {
- const { id: postId } = usePromise(params)
+export default function JoinGroupInterviewPage({ params }: { params: { id: string } }) {
+ const { id: postId } = paramsThis prevents relying on an unstable React API and simplifies the component.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { useState, use as usePromise } from "react" | |
| import Link from "next/link" | |
| import { Button } from "@/components/ui/button" | |
| import { ArrowLeft, ArrowRight, Info } from "lucide-react" | |
| import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" | |
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" | |
| import { Label } from "@/components/ui/label" | |
| import { useQuery, useMutation } from '@tanstack/react-query' | |
| import { getGroupInterviewDetail } from '@/api/interview' | |
| import { getResumeList } from '@/api/resume' | |
| import { findMyCoverletter } from '@/api/coverletter' | |
| import { createMemberInterview } from '@/api/interview' | |
| import { useMemberSession } from '@/components/member-session-context' | |
| export default function JoinGroupInterviewPage({ params }: { params: { id: string } }) { | |
| const postId = params.id | |
| export default function JoinGroupInterviewPage({ params }: { params: Promise<{ id: string }> }) { | |
| const { id: postId } = usePromise(params) | |
| const { memberId } = useMemberSession() | |
| import { useState } from "react" | |
| import Link from "next/link" | |
| import { Button } from "@/components/ui/button" | |
| import { ArrowLeft, ArrowRight, Info } from "lucide-react" | |
| import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card" | |
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" | |
| import { Label } from "@/components/ui/label" | |
| import { useQuery, useMutation } from '@tanstack/react-query' | |
| import { getGroupInterviewDetail } from '@/api/interview' | |
| import { getResumeList } from '@/api/resume' | |
| import { findMyCoverletter } from '@/api/coverletter' | |
| import { createMemberInterview } from '@/api/interview' | |
| import { useMemberSession } from '@/components/member-session-context' | |
| export default function JoinGroupInterviewPage({ params }: { params: { id: string } }) { | |
| const { id: postId } = params | |
| const { memberId } = useMemberSession() | |
| // ... | |
| } |
🤖 Prompt for AI Agents
In app/workspace/interview/group/community/[id]/join/page.tsx around lines 3 to
19, the component currently types the params prop as a Promise and uses the
experimental React use() hook to unwrap it. Since this is a client component and
params are passed synchronously, remove the Promise type from params and
eliminate the use() call. Instead, access params.id directly without awaiting or
unwrapping, simplifying the component and avoiding unstable React APIs.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
app/workspace/interview/group/community/[id]/join/page.tsx (1)
3-19: RemovePromise-based params & experimentaluse()hook as recommended in previous review.This issue was flagged in a previous review but remains unaddressed. Since this is a client component,
paramsshould be accessed synchronously.Apply the previously suggested fix:
-import { useState, use as usePromise } from "react" +import { useState } from "react" … -export default function JoinGroupInterviewPage({ params }: { params: Promise<{ id: string }> }) { - const { id: postId } = usePromise(params) +export default function JoinGroupInterviewPage({ params }: { params: { id: string } }) { + const { id: postId } = params
🧹 Nitpick comments (1)
app/workspace/interview/group/community/[id]/join/page.tsx (1)
154-162: Improve accessibility and user experience for empty states.The current implementation shows disabled placeholder text for empty lists, but this creates poor accessibility and UX.
<SelectContent> - {resumes.length === 0 ? ( - <div className="px-3 py-2 text-gray-400 text-sm">이력서가 없습니다</div> - ) : ( + {resumes.length === 0 ? ( + <SelectItem value="" disabled> + 이력서가 없습니다 + </SelectItem> + ) : ( resumes.map((resume) => ( <SelectItem key={resume.resumeId} value={String(resume.resumeId)}> {resume.fileName} </SelectItem> )) - )} + )}Apply the same pattern to cover letters. This ensures proper Select component behavior and accessibility.
Also applies to: 174-182
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
app/workspace/interview/group/community/[id]/join/page.tsx(7 hunks)components/interview/steps/Step4Group.tsx(1 hunks)
🔇 Additional comments (1)
components/interview/steps/Step4Group.tsx (1)
92-97: LGTM! Logical improvement to form state management.The change appropriately sets
startTypeto"scheduled"when a date is selected, ensuring consistent form state. This improves the user experience by automatically configuring the interview type based on the user's date selection.
| // 에러/호스트 본인 체크 후 뒤로 이동 처리 | ||
| if (!interviewLoading && !resumeLoading && !coverletterLoading) { | ||
| if (interviewError || resumeError || coverletterError || !post) { | ||
| alert('데이터를 불러오지 못했습니다.') | ||
| if (typeof window !== 'undefined') window.history.back() | ||
| return null | ||
| } | ||
| if (memberId && post.hostName && String(memberId) === post.hostName) { | ||
| alert('본인은 자신의 면접에 참여할 수 없습니다.') | ||
| if (typeof window !== 'undefined') window.history.back() | ||
| return null | ||
| } | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Improve error handling and navigation.
The current error handling has several issues:
- Using
window.history.back()can be unreliable - Multiple conditions in a single block make debugging difficult
- Browser compatibility concerns with window checks
- // 에러/호스트 본인 체크 후 뒤로 이동 처리
- if (!interviewLoading && !resumeLoading && !coverletterLoading) {
- if (interviewError || resumeError || coverletterError || !post) {
- alert('데이터를 불러오지 못했습니다.')
- if (typeof window !== 'undefined') window.history.back()
- return null
- }
- if (memberId && post.hostName && String(memberId) === post.hostName) {
- alert('본인은 자신의 면접에 참여할 수 없습니다.')
- if (typeof window !== 'undefined') window.history.back()
- return null
- }
- }
+ const router = useRouter()
+
+ // Error handling
+ useEffect(() => {
+ if (!interviewLoading && !resumeLoading && !coverletterLoading) {
+ if (interviewError || resumeError || coverletterError || !post) {
+ alert('데이터를 불러오지 못했습니다.')
+ router.back()
+ return
+ }
+ if (memberId && post.hostId && memberId === post.hostId) {
+ alert('본인은 자신의 면접에 참여할 수 없습니다.')
+ router.back()
+ return
+ }
+ }
+ }, [interviewLoading, resumeLoading, coverletterLoading, interviewError, resumeError, coverletterError, post, memberId, router])Don't forget to import useRouter from next/navigation.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In app/workspace/interview/group/community/[id]/join/page.tsx around lines 74 to
86, replace the use of window.history.back() with Next.js's useRouter hook for
navigation to ensure reliability and compatibility. Refactor the error handling
by separating the conditions into distinct if blocks for clearer debugging and
maintainability. Also, add the import statement for useRouter from
next/navigation at the top of the file.
| if (typeof window !== 'undefined') window.history.back() | ||
| return null | ||
| } | ||
| if (memberId && post.hostName && String(memberId) === post.hostName) { |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Fix incorrect host validation logic.
The comparison between memberId (number) and hostName (string) is logically incorrect. If hostName represents the host's name rather than ID, this validation will never work properly.
- if (memberId && post.hostName && String(memberId) === post.hostName) {
+ if (memberId && post.hostId && memberId === post.hostId) {Alternatively, if the API doesn't provide hostId, verify the correct field name in the interview data structure to properly identify the host.
🏁 Script executed:
#!/bin/bash
# Verify the structure of GroupInterviewDetailDTO to identify the correct host identifier field
ast-grep --pattern 'interface GroupInterviewDetailDTO {
$$$
}'Length of output: 862
Fix host validation logic
The GroupInterviewDetailDTO includes only hostName?: string (no hostId), so this check will always fail—it’s comparing a numeric memberId to a user name:
if (memberId && post.hostName && String(memberId) === post.hostName) { … }To correct this:
- Preferred: Extend the API/DTO to provide a
hostId: number, then update to:- if (memberId && post.hostName && String(memberId) === post.hostName) { + if (memberId && post.hostId && memberId === post.hostId) { // host-only logic }
- Alternative (no API change): Derive the host’s ID from
groupInterviewParticipants. For example, if participants include anisHostflag or role:const host = post.groupInterviewParticipants?.find(p => p.isHost); if (memberId && host && memberId === host.memberId) { // host-only logic }
Ensure the front end and API agree on which field identifies the host.
🤖 Prompt for AI Agents
In app/workspace/interview/group/community/[id]/join/page.tsx at line 81, the
current host validation incorrectly compares memberId (a number) to hostName (a
string), which will always fail. To fix this, update the logic to identify the
host by their memberId instead of hostName. If the API does not provide a hostId
directly, find the host from post.groupInterviewParticipants by checking for an
isHost flag or equivalent, then compare memberId to that participant's memberId
for validation. Adjust the code to use this correct host identification method
to ensure proper host validation.
Summary by CodeRabbit
New Features
Bug Fixes
Refactor