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>
This commit is contained in:
Daniel Noulet 2026-09-19 22:13:50 +02:00
parent afc8f0d5a7
commit e4fee06e2f
2 changed files with 27 additions and 1 deletions

View file

@ -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')

View file

@ -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<ScheduledInterview['notes']>([])
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 <Loading />
if (!interview) return <ErrorAlert error={error} />
return (
@ -236,6 +247,15 @@ export function ScheduledInterviewPage() {
{error && <ErrorAlert error={error} />}
<DateAndPlace interview={interview} />
<div className="session-actions">
<button
className="button secondary"
type="button"
onClick={() => void copyParticipantLink(interview.candidateUrl)}
aria-label="Copy participant interview link"
>
{linkCopied ? <Check size={17} /> : <Copy size={17} />}
{linkCopied ? 'Link copied' : 'Copy participant link'}
</button>
{interview.status !== 'started' && interview.status !== 'completed' && (
<button
className="button primary"