feat: guest email field on QR review form for reply
Some checks failed
Deploy to Production / deploy (push) Has been cancelled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 17:43:49 +03:00
parent 8ab71b1e45
commit 9bda48c020
5 changed files with 41 additions and 22 deletions

View File

@@ -1980,6 +1980,7 @@ export interface ReviewApi {
bookingId: string | null
guestId: string | null
guestName: string
guestEmail: string | null
source: string
rating: number
text: string | null

View File

@@ -24,6 +24,7 @@ export function GuestReviewQrPage() {
const [rating, setRating] = useState(0)
const [hover, setHover] = useState(0)
const [text, setText] = useState('')
const [email, setEmail] = useState('')
const [submitted, setSubmitted] = useState(false)
const [submitting, setSubmitting] = useState(false)
@@ -45,7 +46,7 @@ export function GuestReviewQrPage() {
await fetch(`${BASE}/api/public/review-qr/${slug}/${encodeURIComponent(room)}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ rating, text: text.trim() || undefined }),
body: JSON.stringify({ rating, text: text.trim() || undefined, email: email.trim() || undefined }),
})
setSubmitted(true)
} catch {
@@ -143,6 +144,16 @@ export function GuestReviewQrPage() {
/>
)}
{rating > 0 && (
<input
type="email"
className="w-full border border-slate-200 rounded-2xl p-3.5 text-sm text-slate-700 bg-slate-50 focus:outline-none focus:border-blue-400 transition-colors placeholder:text-slate-400"
placeholder="Ваш email для ответа (необязательно)"
value={email}
onChange={e => setEmail(e.target.value)}
/>
)}
<button
onClick={handleSubmit}
disabled={rating === 0 || submitting}

View File

@@ -923,31 +923,33 @@ export function ReviewsPage() {
)}
{replyId === review.id && (
<div className="mt-3 space-y-2">
{review.source === 'qr' && (
<input
type="email"
className="input text-sm w-full"
placeholder="Email гостя (необязательно — для отправки ответа)"
value={replyEmail}
onChange={e => setReplyEmail(e.target.value)}
autoFocus
/>
{review.source === 'qr' && !review.guestEmail && (
<p className="text-xs text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/20 px-3 py-2 rounded-lg">
Гость не указал email ответ сохранится как внутренняя заметка
</p>
)}
{review.source === 'qr' && review.guestEmail && (
<p className="text-xs text-emerald-600 dark:text-emerald-400 bg-emerald-50 dark:bg-emerald-900/20 px-3 py-2 rounded-lg">
Ответ отправится на: {review.guestEmail}
</p>
)}
<textarea
className="input resize-none text-sm w-full" rows={2}
placeholder={review.source === 'qr'
? replyEmail ? 'Ответ отправится на указанный email...' : 'Внутренняя заметка (без email — не отправится гостю)...'
placeholder={review.source === 'qr' && !review.guestEmail
? 'Внутренняя заметка (гость не оставил email)...'
: 'Личный ответ гостю (отправится по email)...'}
value={replyText}
onChange={e => setReplyText(e.target.value)}
autoFocus={review.source !== 'qr'}
autoFocus
/>
<div className="flex gap-2">
<button
onClick={() => sendReply(review.id, review.source === 'qr')}
onClick={() => sendReply(review.id, false)}
className="btn-primary text-xs py-1.5"
>
{review.source === 'qr' && replyEmail ? 'Отправить на email' : review.source === 'qr' ? 'Сохранить заметку' : 'Отправить гостю'}
{review.source === 'qr' && !review.guestEmail
? 'Сохранить заметку'
: 'Отправить гостю'}
</button>
<button onClick={() => { setReplyId(null); setReplyEmail('') }} className="btn-secondary text-xs py-1.5">Отмена</button>
</div>