From e4fee06e2fc02ad6d75df1ebb3279a7bf08dd37f Mon Sep 17 00:00:00 2001 From: Daniel Noulet Date: Sat, 19 Sep 2026 22:13:50 +0200 Subject: [PATCH] feat: add participant interview link action Add a copy button to scheduled interview sessions with confirmation, clipboard error handling, and regression coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- frontend/src/app/App.test.tsx | 6 ++++++ frontend/src/features/interviews/pages.tsx | 22 +++++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/App.test.tsx b/frontend/src/app/App.test.tsx index 35643f1..2eaf2b5 100644 --- a/frontend/src/app/App.test.tsx +++ b/frontend/src/app/App.test.tsx @@ -814,6 +814,7 @@ describe('Go Guess frontend', () => { it('edits shared documentation and displays notes received over authenticated fetch streaming', async () => { authenticate() const user = userEvent.setup() + const writeText = vi.spyOn(navigator.clipboard, 'writeText') const scheduled = { id: 80, jobId: 7, @@ -866,6 +867,11 @@ describe('Go Guess frontend', () => { }) renderApp('/scheduled-interviews/80') expect(await screen.findByText('Strong system design answer.')).toBeInTheDocument() + await user.click(screen.getByRole('button', { name: 'Copy participant interview link' })) + expect(writeText).toHaveBeenCalledWith('/participant/meeting/meeting-token') + expect( + screen.getByRole('button', { name: 'Copy participant interview link' }), + ).toHaveTextContent('Link copied') const document = screen.getByLabelText('Shared documentation') await user.clear(document) await user.type(document, 'Updated candidate exercise') diff --git a/frontend/src/features/interviews/pages.tsx b/frontend/src/features/interviews/pages.tsx index 83b9bec..d51336e 100644 --- a/frontend/src/features/interviews/pages.tsx +++ b/frontend/src/features/interviews/pages.tsx @@ -1,4 +1,4 @@ -import { Check, UsersRound, X } from 'lucide-react' +import { Check, Copy, UsersRound, X } from 'lucide-react' import { type FormEvent, useEffect, useMemo, useState } from 'react' import { Link, useParams } from 'react-router-dom' import { api, ApiError } from '../../api/client' @@ -145,6 +145,7 @@ export function ScheduledInterviewPage() { const [notes, setNotes] = useState([]) const [error, setError] = useState('') const [saving, setSaving] = useState(false) + const [linkCopied, setLinkCopied] = useState(false) useEffect(() => { let active = true @@ -218,6 +219,16 @@ export function ScheduledInterviewPage() { } } + async function copyParticipantLink(url: string) { + setError('') + try { + await navigator.clipboard.writeText(url) + setLinkCopied(true) + } catch { + setError('Could not copy the participant interview link') + } + } + if (!interview && !error) return if (!interview) return return ( @@ -236,6 +247,15 @@ export function ScheduledInterviewPage() { {error && }
+ {interview.status !== 'started' && interview.status !== 'completed' && (