frontendt / src /components /InterviewSetup.jsx
ayushsinghal1510's picture
Added coaching and hotel
252a04f
Raw
History Blame Contribute Delete
12.5 kB
import { useState, useMemo } from 'react'
import { useNavigate } from 'react-router-dom'
import { COMPANIES } from '../lib/companies'
import VoiceSettings from './VoiceSettings'
import { buildInterviewUrl, buildWatchUrl } from '../lib/sessionConfig'
import { VX_SERVER } from '../lib/webrtc'
const DEFAULT_KEY = 'vk_Qg95CQBuNFuiiHMCVtgo9bh9kx0N1ACnaj8zdJyg5qsBDWTjxt2ZoegM2UCdKfCY'
export default function InterviewSetup() {
const navigate = useNavigate()
const sessionId = useMemo(() => crypto.randomUUID(), [])
const [phase, setPhase] = useState('setup') // 'setup' | 'ready'
const [name, setName] = useState('')
const [company, setCompany] = useState(null)
const [error, setError] = useState('')
const [voiceSettings, setVoiceSettings] = useState({})
// Generated URLs — only set after clicking Create
const [interviewUrl, setInterviewUrl] = useState('')
const [watchUrl, setWatchUrl] = useState('')
// Copy states per link
const [copiedInterview, setCopiedInterview] = useState(false)
const [copiedWatch, setCopiedWatch] = useState(false)
function copy(text, setDone) {
navigator.clipboard.writeText(text).then(() => {
setDone(true)
setTimeout(() => setDone(false), 2000)
})
}
function handleCreate() {
if (!name.trim()) return setError('Enter the candidate name')
if (!company) return setError('Choose a company')
setError('')
const config = {
candidateName: name.trim(),
company,
apiKey: DEFAULT_KEY,
server: VX_SERVER,
voiceSettings: Object.keys(voiceSettings).length ? voiceSettings : null,
}
setInterviewUrl(buildInterviewUrl(sessionId, config))
setWatchUrl(buildWatchUrl(sessionId))
setPhase('ready')
}
if (phase === 'ready') {
return (
<div style={s.page}>
<div style={s.glow1} />
<div style={s.glow2} />
<div style={s.inner}>
<button style={s.back} onClick={() => setPhase('setup')}>← reconfigure</button>
<p style={s.pill}>Interview ready</p>
<h1 style={s.title}>
Send the link,<br />
<span style={{ color: 'var(--accent)' }}>then watch.</span>
</h1>
<p style={s.subtitle}>
Share the interview link with <strong style={{ color: 'var(--text)' }}>{name}</strong>.
Open the watch link on your device to follow the conversation live.
</p>
{/* Interview link */}
<div style={s.linkCard}>
<div style={s.linkCardHeader}>
<span style={s.linkCardLabel}>Interview link</span>
<span style={s.linkCardHint}>Send this to the candidate</span>
</div>
<div style={s.linkRow}>
<span style={s.linkText}>{interviewUrl}</span>
<button
style={{ ...s.copyBtn, ...(copiedInterview ? s.copyBtnDone : {}) }}
onClick={() => copy(interviewUrl, setCopiedInterview)}
>
{copiedInterview ? 'Copied!' : 'Copy'}
</button>
</div>
</div>
{/* Watch link */}
<div style={{ ...s.linkCard, marginBottom: 28 }}>
<div style={s.linkCardHeader}>
<span style={{ ...s.linkCardLabel, color: '#60d0f0' }}>Watch link</span>
<span style={s.linkCardHint}>Your observer view</span>
</div>
<div style={s.linkRow}>
<span style={s.linkText}>{watchUrl}</span>
<button
style={{ ...s.copyBtn, ...(copiedWatch ? s.copyBtnDone : {}) }}
onClick={() => copy(watchUrl, setCopiedWatch)}
>
{copiedWatch ? 'Copied!' : 'Copy'}
</button>
</div>
</div>
<button style={s.watchBtn} onClick={() => navigate(`/watch/${sessionId}`)}>
Open watch room →
</button>
<p style={s.fine}>
The candidate opens their link and the interview starts automatically.
You'll see the full transcript here as it happens.
</p>
</div>
</div>
)
}
return (
<div style={s.page}>
<div style={s.glow1} />
<div style={s.glow2} />
<div style={s.inner}>
<button style={s.back} onClick={() => navigate('/')}>← back</button>
<p style={s.pill}>AI-powered mock interview</p>
<h1 style={s.title}>
Configure &amp;<br />
<span style={{ color: 'var(--accent)' }}>send the link.</span>
</h1>
{/* Candidate name */}
<div style={s.fieldGroup}>
<label style={s.label}>Candidate name</label>
<input
style={s.input}
value={name}
onChange={e => setName(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleCreate()}
placeholder="e.g. Ayush"
autoComplete="off"
/>
</div>
{/* Company picker */}
<div style={s.fieldGroup}>
<label style={s.label}>Interview for</label>
<div style={s.companyGrid}>
{Object.values(COMPANIES).map(c => (
<CompanyCard
key={c.id}
company={c}
selected={company === c.id}
onSelect={() => { setCompany(c.id); setError('') }}
/>
))}
</div>
</div>
<VoiceSettings value={voiceSettings} onChange={setVoiceSettings} />
{error && <p style={s.error}>{error}</p>}
<button style={s.startBtn} onClick={handleCreate}>
Create interview →
</button>
<p style={s.fine}>
Generates a unique link for the candidate and a watch link for you.
</p>
</div>
</div>
)
}
function CompanyCard({ company, selected, onSelect }) {
return (
<button
onClick={onSelect}
style={{
...s.companyCard,
borderColor: selected ? company.accent : 'var(--border2)',
background: selected ? `${company.accent}10` : 'var(--surface)',
}}
>
<span style={{ ...s.companyIcon, background: `${company.accent}20`, color: company.accent }}>
{company.icon}
</span>
<div style={s.companyInfo}>
<span style={s.companyName}>{company.name}</span>
<span style={s.companyTag}>{company.tagline}</span>
<div style={s.companyPills}>
{company.questionStyle.map(q => (
<span key={q} style={s.qpill}>{q}</span>
))}
</div>
</div>
{selected && <span style={{ ...s.checkmark, color: company.accent }}></span>}
</button>
)
}
const s = {
page: {
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '40px 20px',
position: 'relative',
overflowY: 'auto',
overflowX: 'hidden',
},
glow1: {
position: 'fixed', inset: 0,
background: 'radial-gradient(ellipse 60% 50% at 20% 80%, rgba(200,240,96,.05) 0%, transparent 70%)',
pointerEvents: 'none',
},
glow2: {
position: 'fixed', inset: 0,
background: 'radial-gradient(ellipse 50% 50% at 80% 20%, rgba(96,208,240,.04) 0%, transparent 70%)',
pointerEvents: 'none',
},
inner: {
width: '100%',
maxWidth: 560,
display: 'flex',
flexDirection: 'column',
position: 'relative',
zIndex: 1,
},
back: {
alignSelf: 'flex-start',
fontFamily: 'var(--mono)',
fontSize: 11,
color: 'var(--muted)',
background: 'none',
border: 'none',
cursor: 'pointer',
letterSpacing: '.08em',
padding: 0,
marginBottom: 28,
},
pill: {
display: 'inline-block',
alignSelf: 'flex-start',
fontFamily: 'var(--mono)',
fontSize: 10,
letterSpacing: '.2em',
textTransform: 'uppercase',
color: 'var(--accent)',
border: '1px solid rgba(200,240,96,.25)',
padding: '4px 10px',
borderRadius: 20,
marginBottom: 20,
},
title: {
fontSize: 'clamp(34px, 6vw, 56px)',
fontWeight: 800,
lineHeight: .95,
letterSpacing: '-.03em',
marginBottom: 16,
},
subtitle: {
fontFamily: 'var(--mono)',
fontSize: 11,
color: 'var(--muted)',
lineHeight: 1.7,
marginBottom: 32,
letterSpacing: '.04em',
},
linkCard: {
background: 'var(--surface)',
border: '1px solid var(--border2)',
borderRadius: 10,
padding: '14px 16px',
marginBottom: 14,
display: 'flex',
flexDirection: 'column',
gap: 10,
},
linkCardHeader: {
display: 'flex',
alignItems: 'baseline',
gap: 10,
},
linkCardLabel: {
fontFamily: 'var(--mono)',
fontSize: 9,
letterSpacing: '.2em',
textTransform: 'uppercase',
color: 'var(--accent)',
},
linkCardHint: {
fontFamily: 'var(--mono)',
fontSize: 9,
color: 'var(--muted)',
letterSpacing: '.08em',
},
linkRow: {
display: 'flex',
alignItems: 'center',
gap: 10,
},
linkText: {
fontFamily: 'var(--mono)',
fontSize: 10,
color: 'var(--muted)',
flex: 1,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
},
copyBtn: {
fontFamily: 'var(--mono)',
fontSize: 10,
letterSpacing: '.1em',
padding: '5px 12px',
borderRadius: 6,
border: '1px solid var(--border2)',
background: 'transparent',
color: 'var(--text)',
cursor: 'pointer',
flexShrink: 0,
transition: 'border-color .15s, color .15s',
},
copyBtnDone: {
color: 'var(--accent)',
borderColor: 'var(--accent)',
},
fieldGroup: {
display: 'flex',
flexDirection: 'column',
gap: 8,
marginBottom: 20,
},
label: {
fontFamily: 'var(--mono)',
fontSize: 10,
letterSpacing: '.18em',
color: 'var(--muted)',
textTransform: 'uppercase',
},
input: {
background: 'var(--surface)',
border: '1px solid var(--border2)',
color: 'var(--text)',
fontFamily: 'var(--mono)',
fontSize: 13,
padding: '12px 14px',
borderRadius: 8,
outline: 'none',
width: '100%',
},
companyGrid: {
display: 'flex',
flexDirection: 'column',
gap: 10,
},
companyCard: {
display: 'flex',
alignItems: 'flex-start',
gap: 14,
padding: '14px 16px',
borderRadius: 10,
border: '1.5px solid',
cursor: 'pointer',
textAlign: 'left',
transition: 'border-color .15s, background .15s',
fontFamily: 'var(--sans)',
width: '100%',
position: 'relative',
},
companyIcon: {
width: 40,
height: 40,
borderRadius: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
fontSize: 18,
fontWeight: 800,
flexShrink: 0,
},
companyInfo: {
display: 'flex',
flexDirection: 'column',
gap: 3,
flex: 1,
},
companyName: {
fontSize: 15,
fontWeight: 700,
color: 'var(--text)',
},
companyTag: {
fontFamily: 'var(--mono)',
fontSize: 10,
color: 'var(--muted)',
letterSpacing: '.12em',
},
companyPills: {
display: 'flex',
flexWrap: 'wrap',
gap: 4,
marginTop: 6,
},
qpill: {
fontFamily: 'var(--mono)',
fontSize: 9,
letterSpacing: '.1em',
color: 'var(--muted)',
border: '1px solid var(--border)',
padding: '2px 7px',
borderRadius: 20,
},
checkmark: {
position: 'absolute',
top: 12,
right: 14,
fontSize: 16,
fontWeight: 700,
},
error: {
fontFamily: 'var(--mono)',
fontSize: 11,
color: 'var(--danger)',
marginBottom: 12,
},
startBtn: {
background: 'var(--accent)',
color: '#0a0a0a',
border: 'none',
fontFamily: 'var(--sans)',
fontSize: 14,
fontWeight: 700,
letterSpacing: '.06em',
padding: '16px 24px',
borderRadius: 8,
cursor: 'pointer',
marginTop: 4,
marginBottom: 16,
},
watchBtn: {
background: 'transparent',
color: 'var(--text)',
border: '1.5px solid var(--border2)',
fontFamily: 'var(--sans)',
fontSize: 14,
fontWeight: 600,
letterSpacing: '.04em',
padding: '14px 24px',
borderRadius: 8,
cursor: 'pointer',
marginBottom: 20,
transition: 'border-color .15s',
},
fine: {
fontFamily: 'var(--mono)',
fontSize: 10,
color: 'var(--muted)',
letterSpacing: '.1em',
textAlign: 'center',
lineHeight: 1.6,
},
}