import { useEffect, useState } from 'react';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { useUploadFile } from '@/hooks/useUploadFile';
import { useToast } from '@/hooks/useToast';
import { PhotoDropzone } from '@/components/PhotoDropzone';
import { ImageWithFallback } from '@/components/ImageWithFallback';
import { useTranslation } from '@/contexts/language';
import { normalizeUrlInput } from '@/lib/sanitizeUrl';
import { cn } from '@/lib/utils';
import { Check, X } from 'lucide-react';
import {
ALL_RATE_UNITS,
AVAILABILITY_OPTIONS,
DEFAULT_RATE_UNITS,
PAYMENT_METHODS,
RATE_CURRENCIES,
type LabOption,
} from '@/lib/labExtras';
interface LabExtrasFieldsProps {
/** Raw tags of the existing profile, for prefill on edit. */
existingTags?: string[][];
/**
* Called with the COMPLETE set of tags this component manages (after every
* change, including the initial prefill), so the host form can append them on
* publish without owning any of this state.
*/
onChange: (tags: string[][]) => void;
accentText: string;
accentChip: string;
accentChipHover: string;
/** Billing units relevant to this craft (defaults to a generic set). */
rateUnits?: LabOption[];
/** Hide the portfolio gallery (default false). */
hideGallery?: boolean;
/** Hide the highlight-reel video field (default false). */
hideVideo?: boolean;
}
/** Tag values this component always owns, so prefill can clear stale ones. */
function Chips({
options,
selected,
onToggle,
accentChip,
accentChipHover,
t,
}: {
options: LabOption[];
selected: string[];
onToggle: (value: string) => void;
accentChip: string;
accentChipHover: string;
t: (key: string) => string;
}) {
return (
{options.map((o) => {
const active = selected.includes(o.value);
return (
onToggle(o.value)}
aria-pressed={active}
className={cn(
'min-h-0 min-w-0 rounded-full border px-3 py-1.5 text-xs font-bold transition-all active:scale-95',
active ? accentChip : cn('bg-white/5 border-white/10 text-gray-400', accentChipHover),
)}
>
{t(o.key)}
);
})}
);
}
/**
* The shared, craft-agnostic enrichment block for every Lab provider form:
* languages, availability, structured rate, payments (+ PWYC + booking fee),
* portfolio gallery, highlight reel and a booking link.
*
* Self-contained: it prefills from `existingTags`, owns its state, and reports
* the full managed tag set via `onChange` so host forms stay tiny.
*/
export function LabExtrasFields({
existingTags,
onChange,
accentText,
accentChip,
accentChipHover,
rateUnits = DEFAULT_RATE_UNITS,
hideGallery = false,
hideVideo = false,
}: LabExtrasFieldsProps) {
const { t } = useTranslation();
const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile();
const { toast } = useToast();
const [languages, setLanguages] = useState([]);
const [languageInput, setLanguageInput] = useState('');
const [availability, setAvailability] = useState([]);
const [rateAmount, setRateAmount] = useState('');
const [rateCurrency, setRateCurrency] = useState('sats');
const [rateUnit, setRateUnit] = useState(rateUnits[0]?.value ?? 'hour');
const [paymentMethods, setPaymentMethods] = useState([]);
const [paymentNote, setPaymentNote] = useState('');
const [bookingFee, setBookingFee] = useState('');
const [bookingFeeCurrency, setBookingFeeCurrency] = useState('sats');
const [payWhatYouCan, setPayWhatYouCan] = useState(false);
const [photos, setPhotos] = useState([]);
const [video, setVideo] = useState('');
const [booking, setBooking] = useState('');
const [prefilled, setPrefilled] = useState(false);
// Prefill once, when the (async) existing profile arrives.
useEffect(() => {
if (prefilled || !existingTags) return;
setPrefilled(true);
const tag = (n: string) => existingTags.find(([t]) => t === n)?.[1];
const vals = (n: string, lower = true) => existingTags.filter(([t]) => t === n).map(([, v]) => (lower ? v.toLowerCase() : v));
setLanguages(vals('language', false));
setAvailability(vals('availability'));
setRateAmount(tag('rate_amount') ?? '');
setRateCurrency(tag('rate_currency') ?? 'sats');
setRateUnit(tag('rate_unit') ?? rateUnits[0]?.value ?? 'hour');
setPaymentMethods(vals('payment_method'));
setPaymentNote(tag('payment_note') ?? '');
setBookingFee(tag('booking_fee') ?? '');
setBookingFeeCurrency(tag('booking_fee_currency') ?? 'sats');
setPayWhatYouCan(existingTags.some(([t, v]) => t === 'pay_what_you_can' && v === 'true'));
setPhotos(vals('photo', false));
setVideo(tag('video') ?? '');
setBooking(tag('booking') ?? '');
}, [existingTags, prefilled, rateUnits]);
// Report the full managed tag set whenever anything changes.
useEffect(() => {
const tags: string[][] = [];
for (const v of languages) tags.push(['language', v]);
for (const v of availability) tags.push(['availability', v]);
if (rateAmount.trim()) {
tags.push(['rate_amount', rateAmount.trim()]);
tags.push(['rate_currency', rateCurrency]);
tags.push(['rate_unit', rateUnit]);
}
for (const m of paymentMethods) tags.push(['payment_method', m]);
if (paymentNote.trim()) tags.push(['payment_note', paymentNote.trim()]);
if (bookingFee.trim()) {
tags.push(['booking_fee', bookingFee.trim()]);
tags.push(['booking_fee_currency', bookingFeeCurrency]);
}
if (payWhatYouCan) tags.push(['pay_what_you_can', 'true']);
for (const p of photos) tags.push(['photo', p]);
if (video.trim()) tags.push(['video', video.trim()]);
if (booking.trim()) tags.push(['booking', normalizeUrlInput(booking)]);
onChange(tags);
}, [languages, availability, rateAmount, rateCurrency, rateUnit, paymentMethods, paymentNote, bookingFee, bookingFeeCurrency, payWhatYouCan, photos, video, booking, onChange]);
const toggle = (list: string[], setList: (v: string[]) => void, value: string) =>
setList(list.includes(value) ? list.filter((v) => v !== value) : [...list, value]);
const addLanguage = () => {
const v = languageInput.trim();
if (!v) return;
setLanguages((prev) => (prev.some((l) => l.toLowerCase() === v.toLowerCase()) ? prev : [...prev, v]));
setLanguageInput('');
};
const handleGalleryUpload = async (files: File[]) => {
const room = 12 - photos.length;
if (room <= 0) return;
try {
const uploaded: string[] = [];
for (const file of files.slice(0, room)) {
const tags = await uploadFile(file);
const url = tags[0]?.[1];
if (url) uploaded.push(url);
}
if (uploaded.length > 0) {
setPhotos((prev) => [...prev, ...uploaded].slice(0, 12));
toast({ title: t('labUi.coachForm.galleryUploaded', { count: uploaded.length }) });
}
} catch (error) {
toast({ title: t('labUi.become.uploadFailed'), description: error instanceof Error ? error.message : t('common.error'), variant: 'destructive' });
}
};
return (
{/* Languages */}
{t('labUi.coachForm.languagesLabel')}
{t('labUi.coachForm.languagesHint')}
setLanguageInput(e.target.value)}
onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); addLanguage(); } }}
placeholder={t('labUi.coachForm.languagePlaceholder')}
className="bg-white/5 border-white/10"
/>
{t('common.add')}
{languages.length > 0 && (
{languages.map((lang) => (
{lang}
setLanguages((prev) => prev.filter((l) => l !== lang))} aria-label={`${t('common.remove')} ${lang}`} className="min-h-0 min-w-0 text-gray-500 hover:text-white">
))}
)}
{/* Availability + booking link */}
{t('labUi.coachForm.availabilityLabel')}
toggle(availability, setAvailability, v)} accentChip={accentChip} accentChipHover={accentChipHover} t={t} />
{/* Structured rate */}
{/* Payments */}
{t('labUi.coachForm.sectionPayments')}
{t('labUi.coachForm.paymentMethodsHint')}
toggle(paymentMethods, setPaymentMethods, v)} accentChip={accentChip} accentChipHover={accentChipHover} t={t} />
setPayWhatYouCan((v) => !v)}
aria-pressed={payWhatYouCan}
className={cn(
'flex w-full min-h-0 items-center gap-2 rounded-lg border px-3 py-2.5 text-left text-sm font-bold transition-colors',
payWhatYouCan ? 'border-white/20 bg-white/10 text-white' : 'border-white/10 bg-white/5 text-gray-300 hover:bg-white/10',
)}
>
{payWhatYouCan && }
{t('labUi.coachForm.payWhatYouCanLabel')}
{t('labUi.coachForm.paymentNoteLabel')}
setPaymentNote(e.target.value)}
placeholder={t('labUi.coachForm.paymentNotePlaceholder')}
className="mt-1.5 bg-white/5 border-white/10"
/>
{/* Portfolio gallery + reel */}
{(!hideGallery || !hideVideo) && (
{t('labUi.coachForm.sectionPortfolio')}
{!hideGallery && (
{t('labUi.coachForm.galleryHint')}
{photos.length > 0 && (
{photos.map((url) => (
setPhotos((prev) => prev.filter((p) => p !== url))} aria-label={t('common.remove')} className="absolute right-1 top-1 min-h-0 min-w-0 rounded-full bg-black/80 p-1 hover:bg-red-500">
))}
)}
{photos.length < 12 && (
)}
)}
{!hideVideo && (
)}
)}
);
}