Add phone number field and WhatsApp/copy message buttons for participants
This commit is contained in:
@@ -15,6 +15,7 @@ interface Activity {
|
||||
interface Participant {
|
||||
id: number
|
||||
name: string
|
||||
phone: string | null
|
||||
token: string
|
||||
created_at: string
|
||||
}
|
||||
@@ -38,6 +39,7 @@ export function QuestionnaireDetail() {
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [copied, setCopied] = useState('')
|
||||
const [newParticipantName, setNewParticipantName] = useState('')
|
||||
const [newParticipantPhone, setNewParticipantPhone] = useState('')
|
||||
const [addingParticipant, setAddingParticipant] = useState(false)
|
||||
|
||||
// Edit activity modal state
|
||||
@@ -89,11 +91,15 @@ export function QuestionnaireDetail() {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ name: newParticipantName.trim() }),
|
||||
body: JSON.stringify({
|
||||
name: newParticipantName.trim(),
|
||||
phone: newParticipantPhone.trim() || null
|
||||
}),
|
||||
})
|
||||
const data = await res.json()
|
||||
if (data.success) {
|
||||
setNewParticipantName('')
|
||||
setNewParticipantPhone('')
|
||||
fetchParticipants()
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -103,6 +109,36 @@ export function QuestionnaireDetail() {
|
||||
}
|
||||
}
|
||||
|
||||
// Get first name from full name
|
||||
function getFirstName(fullName: string): string {
|
||||
const parts = fullName.trim().split(/\s+/)
|
||||
return parts[0] || fullName
|
||||
}
|
||||
|
||||
// Generate invitation message for a participant
|
||||
function getInvitationMessage(participant: Participant, url: string): string {
|
||||
const firstName = getFirstName(participant.name)
|
||||
const title = questionnaire?.title || 'de vragenlijst'
|
||||
return `Hoi ${firstName}, we zijn benieuwd naar je ideeën voor "${title}". Je kunt nieuwe ideeën aandragen en/of stemmen op reeds toegevoegde ideeën via deze link: ${url}. Alvast bedankt voor je hulp!`
|
||||
}
|
||||
|
||||
// Copy invitation message to clipboard
|
||||
function copyInvitation(participant: Participant, url: string) {
|
||||
const message = getInvitationMessage(participant, url)
|
||||
navigator.clipboard.writeText(message)
|
||||
setCopied(`msg-${participant.id}`)
|
||||
setTimeout(() => setCopied(''), 2000)
|
||||
}
|
||||
|
||||
// Generate WhatsApp link
|
||||
function getWhatsAppLink(participant: Participant, url: string): string {
|
||||
const message = getInvitationMessage(participant, url)
|
||||
const encodedMessage = encodeURIComponent(message)
|
||||
// Remove leading + from phone number for wa.me link
|
||||
const phone = participant.phone?.replace(/^\+/, '') || ''
|
||||
return `https://wa.me/${phone}?text=${encodedMessage}`
|
||||
}
|
||||
|
||||
async function handleDeleteParticipant(participantId: number) {
|
||||
if (!confirm('Deze deelnemer verwijderen?')) return
|
||||
|
||||
@@ -256,15 +292,22 @@ export function QuestionnaireDetail() {
|
||||
</p>
|
||||
|
||||
{/* Add Participant Form */}
|
||||
<form onSubmit={handleAddParticipant} className="flex gap-2 mb-4">
|
||||
<form onSubmit={handleAddParticipant} className="flex flex-wrap gap-2 mb-4">
|
||||
<input
|
||||
type="text"
|
||||
value={newParticipantName}
|
||||
onChange={(e) => setNewParticipantName(e.target.value)}
|
||||
placeholder="Naam deelnemer"
|
||||
className="flex-1 px-3 py-2 bg-bg-input border border-border rounded-lg text-sm text-text placeholder-text-faint focus:outline-none focus:border-accent"
|
||||
className="flex-1 min-w-[150px] px-3 py-2 bg-bg-input border border-border rounded-lg text-sm text-text placeholder-text-faint focus:outline-none focus:border-accent"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
type="tel"
|
||||
value={newParticipantPhone}
|
||||
onChange={(e) => setNewParticipantPhone(e.target.value)}
|
||||
placeholder="Telefoon (optioneel, bijv. +31612345678)"
|
||||
className="flex-1 min-w-[200px] px-3 py-2 bg-bg-input border border-border rounded-lg text-sm text-text placeholder-text-faint focus:outline-none focus:border-accent"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={addingParticipant}
|
||||
@@ -276,27 +319,53 @@ export function QuestionnaireDetail() {
|
||||
|
||||
{/* Participants List */}
|
||||
{participants.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-3">
|
||||
{participants.map((participant) => {
|
||||
const participantUrl = `${window.location.origin}/q/access/${participant.token}`
|
||||
return (
|
||||
<div key={participant.id} className="flex items-center gap-2 p-3 bg-bg-input rounded-lg">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-text text-sm">{participant.name}</div>
|
||||
<div className="text-xs font-mono text-text-faint truncate">{participantUrl}</div>
|
||||
<div key={participant.id} className="p-3 bg-bg-input rounded-lg">
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium text-text text-sm">{participant.name}</div>
|
||||
{participant.phone && (
|
||||
<div className="text-xs text-text-muted mt-0.5">📱 {participant.phone}</div>
|
||||
)}
|
||||
<div className="text-xs font-mono text-text-faint truncate mt-1">{participantUrl}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleDeleteParticipant(participant.id)}
|
||||
className="px-2 py-1 text-xs font-medium text-danger hover:bg-danger-muted rounded transition-colors shrink-0"
|
||||
title="Verwijderen"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Action buttons */}
|
||||
<div className="flex flex-wrap gap-2 mt-3 pt-3 border-t border-border-light">
|
||||
<button
|
||||
onClick={() => copyUrl(participantUrl, `p-${participant.id}`)}
|
||||
className="px-3 py-1.5 text-xs font-medium text-text-muted hover:text-text border border-border rounded-lg transition-colors flex items-center gap-1"
|
||||
>
|
||||
🔗 {copied === `p-${participant.id}` ? 'Gekopieerd!' : 'Kopieer Link'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => copyInvitation(participant, participantUrl)}
|
||||
className="px-3 py-1.5 text-xs font-medium text-text-muted hover:text-text border border-border rounded-lg transition-colors flex items-center gap-1"
|
||||
>
|
||||
📋 {copied === `msg-${participant.id}` ? 'Gekopieerd!' : 'Kopieer Bericht'}
|
||||
</button>
|
||||
{participant.phone && (
|
||||
<a
|
||||
href={getWhatsAppLink(participant, participantUrl)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="px-3 py-1.5 text-xs font-medium text-white bg-[#25D366] hover:bg-[#1da851] rounded-lg transition-colors flex items-center gap-1"
|
||||
>
|
||||
💬 WhatsApp
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
onClick={() => copyUrl(participantUrl, `p-${participant.id}`)}
|
||||
className="px-3 py-1 text-xs font-medium text-text-muted hover:text-text border border-border rounded transition-colors"
|
||||
>
|
||||
{copied === `p-${participant.id}` ? 'Gekopieerd!' : 'Link Kopiëren'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleDeleteParticipant(participant.id)}
|
||||
className="px-2 py-1 text-xs font-medium text-danger hover:bg-danger-muted rounded transition-colors"
|
||||
>
|
||||
Verwijderen
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user