Add error for failured to send reaction.

This commit is contained in:
Half-Shot
2024-11-08 12:52:05 +00:00
parent afe64cfe20
commit e98f84eb8f
3 changed files with 133 additions and 87 deletions

View File

@@ -4,11 +4,14 @@
}, },
"action": { "action": {
"close": "Close", "close": "Close",
"close_search": "Close search",
"copy_link": "Copy link", "copy_link": "Copy link",
"edit": "Edit", "edit": "Edit",
"go": "Go", "go": "Go",
"invite": "Invite", "invite": "Invite",
"no": "No", "no": "No",
"open_search": "Open search",
"pick_reaction": "Pick reaction",
"raise_hand_or_send_reaction": "Raise hand or send reaction", "raise_hand_or_send_reaction": "Raise hand or send reaction",
"register": "Register", "register": "Register",
"remove": "Remove", "remove": "Remove",
@@ -58,6 +61,7 @@
"preferences": "Preferences", "preferences": "Preferences",
"profile": "Profile", "profile": "Profile",
"raise_hand": "Raise hand", "raise_hand": "Raise hand",
"search": "Search",
"settings": "Settings", "settings": "Settings",
"unencrypted": "Not encrypted", "unencrypted": "Not encrypted",
"username": "Username", "username": "Username",
@@ -128,6 +132,7 @@
"rageshake_sending": "Sending…", "rageshake_sending": "Sending…",
"rageshake_sending_logs": "Sending debug logs…", "rageshake_sending_logs": "Sending debug logs…",
"rageshake_sent": "Thanks!", "rageshake_sent": "Thanks!",
"reaction_search": "Search reactions…",
"recaptcha_caption": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>", "recaptcha_caption": "This site is protected by ReCAPTCHA and the Google <2>Privacy Policy</2> and <6>Terms of Service</6> apply.<9></9>By clicking \"Register\", you agree to our <12>End User Licensing Agreement (EULA)</12>",
"recaptcha_dismissed": "Recaptcha dismissed", "recaptcha_dismissed": "Recaptcha dismissed",
"recaptcha_not_loaded": "Recaptcha not loaded", "recaptcha_not_loaded": "Recaptcha not loaded",

View File

@@ -64,3 +64,19 @@
.searchForm > label { .searchForm > label {
flex: auto; flex: auto;
} }
.alert {
margin-bottom: var(--cpd-space-3x);
animation: grow-in 200ms;
height: 2.5em;
}
@keyframes grow-in {
from {
height: 0;
}
to {
height: 2.5em;
}
}

View File

@@ -11,6 +11,7 @@ import {
Separator, Separator,
Search, Search,
Form, Form,
Alert,
} from "@vector-im/compound-web"; } from "@vector-im/compound-web";
import { import {
SearchIcon, SearchIcon,
@@ -25,6 +26,7 @@ import {
KeyboardEventHandler, KeyboardEventHandler,
ReactNode, ReactNode,
useCallback, useCallback,
useEffect,
useMemo, useMemo,
useState, useState,
} from "react"; } from "react";
@@ -72,9 +74,11 @@ export function ReactionPopupMenu({
toggleRaisedHand, toggleRaisedHand,
isHandRaised, isHandRaised,
canReact, canReact,
errorText,
}: { }: {
sendReaction: (reaction: ReactionOption) => void; sendReaction: (reaction: ReactionOption) => void;
toggleRaisedHand: () => void; toggleRaisedHand: () => void;
errorText?: string;
isHandRaised: boolean; isHandRaised: boolean;
canReact: boolean; canReact: boolean;
}): ReactNode { }): ReactNode {
@@ -119,6 +123,16 @@ export function ReactionPopupMenu({
); );
const label = isHandRaised ? t("common.raise_hand") : t("common.lower_hand"); const label = isHandRaised ? t("common.raise_hand") : t("common.lower_hand");
return ( return (
<>
{errorText && (
<Alert
className={styles.alert}
type="critical"
title="Something went wrong"
>
{errorText}
</Alert>
)}
<div className={styles.reactionPopupMenu}> <div className={styles.reactionPopupMenu}>
<section className={styles.handRaiseSection}> <section className={styles.handRaiseSection}>
<Tooltip label={label}> <Tooltip label={label}>
@@ -141,7 +155,7 @@ export function ReactionPopupMenu({
required required
value={searchText} value={searchText}
name="reactionSearch" name="reactionSearch"
placeholder="Search reactions…" placeholder={t("reaction_search")}
onChange={onSearch} onChange={onSearch}
onKeyDown={onSearchKeyDown} onKeyDown={onSearchKeyDown}
// This is a reasonable use of autofocus, we are focusing when // This is a reasonable use of autofocus, we are focusing when
@@ -151,7 +165,7 @@ export function ReactionPopupMenu({
/> />
<CpdButton <CpdButton
Icon={CloseIcon} Icon={CloseIcon}
aria-label="close search" aria-label={t("action.close_search")}
size="sm" size="sm"
kind="destructive" kind="destructive"
onClick={() => setIsSearching(false)} onClick={() => setIsSearching(false)}
@@ -180,10 +194,10 @@ export function ReactionPopupMenu({
{!isSearching ? ( {!isSearching ? (
<section> <section>
<li key="search" className={styles.reactionPopupMenuItem}> <li key="search" className={styles.reactionPopupMenuItem}>
<Tooltip label="Search"> <Tooltip label={t("common.search")}>
<CpdButton <CpdButton
iconOnly iconOnly
aria-label="Open reactions search" aria-label={t("action.open_search")}
Icon={SearchIcon} Icon={SearchIcon}
kind="tertiary" kind="tertiary"
onClick={() => setIsSearching(true)} onClick={() => setIsSearching(true)}
@@ -193,6 +207,7 @@ export function ReactionPopupMenu({
</section> </section>
) : null} ) : null}
</div> </div>
</>
); );
} }
@@ -205,24 +220,30 @@ export function ReactionToggleButton({
client, client,
rtcSession, rtcSession,
}: ReactionToggleButtonProps): ReactNode { }: ReactionToggleButtonProps): ReactNode {
const { t } = useTranslation();
const { raisedHands, lowerHand, reactions } = useReactions(); const { raisedHands, lowerHand, reactions } = useReactions();
const [busy, setBusy] = useState(false); const [busy, setBusy] = useState(false);
const userId = client.getUserId()!; const userId = client.getUserId()!;
const isHandRaised = !!raisedHands[userId]; const isHandRaised = !!raisedHands[userId];
const memberships = useMatrixRTCSessionMemberships(rtcSession); const memberships = useMatrixRTCSessionMemberships(rtcSession);
const [showReactionsMenu, setShowReactionsMenu] = useState(false); const [showReactionsMenu, setShowReactionsMenu] = useState(false);
const [errorText, setErrorText] = useState<string>();
useEffect(() => {
// Clear whenever the reactions menu state changes.
setErrorText(undefined);
}, [showReactionsMenu]);
const canReact = !reactions[userId]; const canReact = !reactions[userId];
const sendRelation = useCallback( const sendRelation = useCallback(
async (reaction: ReactionOption) => { async (reaction: ReactionOption) => {
try {
const myMembership = memberships.find((m) => m.sender === userId); const myMembership = memberships.find((m) => m.sender === userId);
if (!myMembership?.eventId) { if (!myMembership?.eventId) {
logger.error("Cannot find own membership event"); throw new Error("Cannot find own membership event");
return;
} }
const parentEventId = myMembership.eventId; const parentEventId = myMembership.eventId;
try {
setBusy(true); setBusy(true);
await client.sendEvent( await client.sendEvent(
rtcSession.room.roomId, rtcSession.room.roomId,
@@ -236,12 +257,14 @@ export function ReactionToggleButton({
name: reaction.name, name: reaction.name,
}, },
); );
setErrorText(undefined);
setShowReactionsMenu(false);
// Do NOT close the menu after this. // Do NOT close the menu after this.
} catch (ex) { } catch (ex) {
setErrorText(ex instanceof Error ? ex.message : "Unknown error");
logger.error("Failed to send reaction", ex); logger.error("Failed to send reaction", ex);
} finally { } finally {
setBusy(false); setBusy(false);
setShowReactionsMenu(false);
} }
}, },
[memberships, client, userId, rtcSession], [memberships, client, userId, rtcSession],
@@ -258,13 +281,12 @@ export function ReactionToggleButton({
setBusy(false); setBusy(false);
} }
} else { } else {
try {
const myMembership = memberships.find((m) => m.sender === userId); const myMembership = memberships.find((m) => m.sender === userId);
if (!myMembership?.eventId) { if (!myMembership?.eventId) {
logger.error("Cannot find own membership event"); throw new Error("Cannot find own membership event");
return;
} }
const parentEventId = myMembership.eventId; const parentEventId = myMembership.eventId;
try {
setBusy(true); setBusy(true);
const reaction = await client.sendEvent( const reaction = await client.sendEvent(
rtcSession.room.roomId, rtcSession.room.roomId,
@@ -278,11 +300,13 @@ export function ReactionToggleButton({
}, },
); );
logger.debug("Sent raise hand event", reaction.event_id); logger.debug("Sent raise hand event", reaction.event_id);
setErrorText(undefined);
setShowReactionsMenu(false);
} catch (ex) { } catch (ex) {
logger.error("Failed to send reaction event", ex); setErrorText(ex instanceof Error ? ex.message : "Unknown error");
logger.error("Failed to raise hand", ex);
} finally { } finally {
setBusy(false); setBusy(false);
setShowReactionsMenu(false);
} }
} }
}; };
@@ -307,12 +331,13 @@ export function ReactionToggleButton({
/> />
<Modal <Modal
open={showReactionsMenu} open={showReactionsMenu}
title="Pick reaction" title={t("action.pick_reaction")}
hideHeader hideHeader
classNameModal={styles.reactionPopupMenuModal} classNameModal={styles.reactionPopupMenuModal}
onDismiss={() => setShowReactionsMenu(false)} onDismiss={() => setShowReactionsMenu(false)}
> >
<ReactionPopupMenu <ReactionPopupMenu
errorText={errorText}
isHandRaised={isHandRaised} isHandRaised={isHandRaised}
canReact={canReact} canReact={canReact}
sendReaction={(reaction) => void sendRelation(reaction)} sendReaction={(reaction) => void sendRelation(reaction)}