import { useEffect, useState } from 'react' import { Icon } from '../../components/Icon' import { Modal } from '../../components/Modal' import { OfferTab } from './OfferTab' import type { useSpeakRequest } from '../../lib/useSpeakRequest' /** * Pasek akcji uczestnika. * * DLACZEGO NAD PANELEM, A NIE W ZAKLADCE * ------------------------------------------------------------------------ * Te trzy akcje musza byc osiagalne **niezaleznie od tego, co uczestnik * ogląda**. Prosba o glos schowana w zakladce "Pytania" jest bezuzyteczna * dokladnie wtedy, gdy jest potrzebna: prowadzacy wlasnie zaprosil do * zabrania glosu, a uczestnik czyta czat. * * Zapytanie ofertowe otwiera sie w oknie, a nie przelacza zakladke: wysylajac * zapytanie w trakcie prezentacji uczestnik nie chce stracic z oczu tego, co * dzieje sie na sali. * * STAN PRZYCISKU PO WYSLANIU * ------------------------------------------------------------------------ * Po wyslaniu zapytania przycisk pokazuje potwierdzenie zamiast zachety. * Bez tego uczestnik, ktory nie widzi zadnej zmiany, klika drugi i trzeci raz, * a handlowiec dostaje trzy zgloszenia od tej samej osoby. Kolejne zapytanie * jest mozliwe, ale wymaga swiadomego klikniecia w "Wyslij kolejne". */ export function MeetingActionBar({ token, speak, /** Prelegent nie prosi o glos: ma go z definicji. */ isSpeaker, /** Tryb otwartego mikrofonu: kazdy mowi bez proszenia o glos. */ openMic, /** * Faktyczne uprawnienie do mikrofonu, prosto z tokenu wydanego przez backend. * * JEDYNE ZRODLO PRAWDY O TYM, CZY UCZESTNIK MOZE MOWIC * --------------------------------------------------------------------- * Wczesniej pasek skladal te odpowiedz sam, ze stanu zgloszenia * (`speak.granted`) i trybu otwartego mikrofonu. Rozjezdzalo sie to * z rzeczywistoscia, bo prawo do audio daje takze rola ACTIVE_PARTICIPANT * przy wlaczonym "wideo dla wszystkich": odebranie glosu nie zmienialo wtedy * uprawnien, ale napis "Masz glos" znikal. Uczestnik widzial zamkniety * mikrofon przy dzialajacym i odwrotnie. */ canPublishAudio, /** * Temat wybrany klikiem w baner. * * Baner z tematem oferty otwiera formularz **tutaj**, a nie u siebie: * okno oferty juz istnieje w pasku akcji wraz z obsluga zgody i stanem * po wyslaniu, a drugie takie okno oznaczaloby dwie kopie tej samej logiki. */ offerTopicFromBanner, onOfferHandled, }: { token: string speak: ReturnType isSpeaker: boolean openMic: boolean canPublishAudio: boolean offerTopicFromBanner?: string | null onOfferHandled?: () => void }) { const [offerOpen, setOfferOpen] = useState<'full' | 'callback' | null>(null) const [offerSent, setOfferSent] = useState(false) /** Temat podstawiony z banera, przekazywany do formularza. */ const [presetTopic, setPresetTopic] = useState(null) /* * Klikniecie w baner z tematem otwiera pelny formularz z tym tematem. * Stan "wyslane" cofamy: uczestnik swiadomie kliknal reklame, wiec chce * wyslac kolejne zapytanie, a nie zobaczyc potwierdzenie poprzedniego. */ useEffect(() => { if (!offerTopicFromBanner) return setPresetTopic(offerTopicFromBanner) setOfferSent(false) setOfferOpen('full') onOfferHandled?.() }, [offerTopicFromBanner, onOfferHandled]) return ( <>
{/* Prelegent nie zglasza sie o glos ani nie wysyla zapytan ofertowych. Zamiast pustego paska pokazujemy mu, gdzie moderowac: kolejka glosu, otwarty mikrofon i moderacja pytan sa w panelu prowadzenia, dostepnym dla zalogowanego prowadzacego. Sala sluzy do obrazu. */} {isSpeaker && ( Prowadzisz spotkanie. Zgłoszeniami do głosu i pytaniami zarządzasz w panelu prowadzenia (otwórz go w panelu, przy tym webinarze). )} {/* W trybie otwartego mikrofonu przycisk zgloszenia znika: proszenie o cos, co juz sie ma, tylko myli. Zostaje informacja, ze mikrofon jest dostepny, bo uczestnik musi wiedziec, ze moze byc slyszany. */} {!isSpeaker && (openMic ? ( Możesz mówić bez zgłaszania się ) : ( ))} {/* Pasek oferty to akcje UCZESTNIKA (zapytanie do handlowca). Prelegent i prowadzacy ich nie wysylaja - kontakt handlowy zglasza osoba ogladajaca, nie ta, ktora prowadzi. Dlatego caly blok tylko dla nie-prelegenta. Moderacja glosu i pytan jest w panelu prowadzenia, nie tutaj. */} {!isSpeaker && (
{offerSent ? ( /* * Potwierdzenie zamiast przyciskow. Zachowujemy mozliwosc wyslania * kolejnego zapytania, ale dopiero po swiadomym kliknieciu: * uczestnik ma zobaczyc, ze pierwsze doszlo. */
Zapytanie wysłane
) : ( <> {/* wyrozniony: to jest akcja, na ktorej zalezy organizatorowi */} )}
)}
{offerOpen && ( { setOfferOpen(null) setPresetTopic(null) }} > { setOfferSent(true) // okno zostaje otwarte na potwierdzeniu z tresc zwrotna // z backendu; uczestnik zamyka je sam, gdy je przeczyta }} /> )} ) } /** * Przycisk prosby o glos. * * Trzy stany, bo trzy rozne sytuacje: nie prosil, czeka, ma glos. Jeden * przycisk przelaczajacy sie miedzy "zglos" i "wycofaj" gubilby stan * oczekiwania, czyli dokladnie ten, w ktorym uczestnik zastanawia sie, * czy zgloszenie w ogole doszlo. */ function SpeakButton({ speak, canPublishAudio, }: { speak: ReturnType canPublishAudio: boolean }) { /* * Napis o mikrofonie idzie za UPRAWNIENIEM, a nie za stanem zgloszenia: * mowi o tym, co uczestnik faktycznie moze, a nie o tym, o co prosil. * Nie twierdzi, ze mikrofon jest wlaczony: sala wlacza go sama po * przyznaniu glosu, ale uczestnik moze go zaraz wyciszyc przyciskiem. * Przycisk "Oddaj glos" zostaje przy zgloszeniu, bo dotyczy wlasnie jego. */ if (speak.granted) { return (
{canPublishAudio ? 'Masz głos, możesz mówić' : 'Masz głos, trwa włączanie mikrofonu...'}
) } if (speak.pending) { return (
Czekasz na głos
) } return ( ) }