Release video rooms as a beta feature (#8431)

* Remove blank header from video room view frame

* Add a beta card for video rooms

* Rename the 'disclaimer' on beta cards to 'FAQ'

Because that's what the section actually gets used as

* Add beta pills to video room creation buttons

* Remove duplicate tooltips from face piles

* Add beta pill to headers of video rooms

* Factor RoomInfoLine out of SpaceRoomView

* Factor RoomPreviewCard out of SpaceRoomView

* Adapt RoomPreviewCard for video rooms

* "New video room" → "Video room"

* Add comment about unused cases in RoomPreviewCard

* Add types

* Clarify !important comments

* Add a reload warning

* Fix the reload warning being the wrong way around

* Fix lints

* Make widgets in video rooms mutable again to de-risk future upgrades

* Ensure that the video channel exists when mounting VideoRoomView

* Fix lint

* Iterate beta reload warning
This commit is contained in:
Robin 2022-06-09 13:07:59 -04:00 committed by GitHub
parent c180708a17
commit 30460943b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 188 additions and 86 deletions

View file

@ -20,6 +20,7 @@ limitations under the License.
background-color: $system; background-color: $system;
border-radius: 8px; border-radius: 8px;
box-sizing: border-box; box-sizing: border-box;
color: $secondary-content;
.mx_BetaCard_columns { .mx_BetaCard_columns {
display: flex; display: flex;
@ -45,14 +46,13 @@ limitations under the License.
.mx_BetaCard_caption { .mx_BetaCard_caption {
font-size: $font-15px; font-size: $font-15px;
line-height: $font-20px; line-height: $font-20px;
color: $secondary-content;
} }
.mx_BetaCard_buttons { .mx_BetaCard_buttons {
display: flex; display: flex;
flex-wrap: wrap-reverse; flex-wrap: wrap-reverse;
gap: 12px; gap: $spacing-12;
margin: 20px auto; margin: $spacing-20 auto 0;
.mx_AccessibleButton { .mx_AccessibleButton {
padding: 7px 40px; padding: 7px 40px;
@ -66,10 +66,16 @@ limitations under the License.
} }
} }
.mx_BetaCard_disclaimer { .mx_BetaCard_refreshWarning {
margin-top: $spacing-8;
font-size: $font-10px;
text-align: center;
}
.mx_BetaCard_faq {
margin-top: $spacing-20;
font-size: $font-12px; font-size: $font-12px;
line-height: $font-15px; line-height: $font-15px;
color: $secondary-content;
> h4 { > h4 {
margin: 12px 0 0; margin: 12px 0 0;
@ -105,7 +111,6 @@ limitations under the License.
margin-top: 4px; margin-top: 4px;
font-size: $font-12px; font-size: $font-12px;
line-height: $font-15px; line-height: $font-15px;
color: $secondary-content;
} }
} }
} }

View file

@ -44,6 +44,10 @@ limitations under the License.
.mx_InviteOnlyIcon_large { .mx_InviteOnlyIcon_large {
margin: 0; margin: 0;
} }
.mx_BetaCard_betaPill {
margin-right: $spacing-8;
}
} }
.mx_RoomHeader_spinner { .mx_RoomHeader_spinner {

View file

@ -105,6 +105,13 @@ limitations under the License.
mask-image: url('$(res)/img/element-icons/call/video-call.svg'); mask-image: url('$(res)/img/element-icons/call/video-call.svg');
} }
} }
// XXX Remove this when video rooms leave beta
.mx_BetaCard_betaPill {
position: absolute;
right: $spacing-24;
top: $spacing-32;
}
} }
h1.mx_RoomPreviewCard_name { h1.mx_RoomPreviewCard_name {

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

View file

@ -123,7 +123,7 @@ const SpaceLandingAddButton = ({ space }) => {
{ canCreateRoom && <> { canCreateRoom && <>
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("New room")} label={_t("New room")}
iconClassName="mx_RoomList_iconPlus" iconClassName="mx_RoomList_iconNewRoom"
onClick={async (e) => { onClick={async (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -135,7 +135,8 @@ const SpaceLandingAddButton = ({ space }) => {
} }
}} }}
/> />
{ videoRoomsEnabled && <IconizedContextMenuOption { videoRoomsEnabled && (
<IconizedContextMenuOption
label={_t("New video room")} label={_t("New video room")}
iconClassName="mx_RoomList_iconNewVideoRoom" iconClassName="mx_RoomList_iconNewVideoRoom"
onClick={async (e) => { onClick={async (e) => {
@ -147,7 +148,10 @@ const SpaceLandingAddButton = ({ space }) => {
defaultDispatcher.fire(Action.UpdateSpaceHierarchy); defaultDispatcher.fire(Action.UpdateSpaceHierarchy);
} }
}} }}
/> } >
<BetaPill />
</IconizedContextMenuOption>
) }
</> } </> }
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("Add existing room")} label={_t("Add existing room")}

View file

@ -79,7 +79,7 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
const { const {
title, title,
caption, caption,
disclaimer, faq,
image, image,
feedbackLabel, feedbackLabel,
feedbackSubheading, feedbackSubheading,
@ -99,6 +99,14 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
</AccessibleButton>; </AccessibleButton>;
} }
let refreshWarning: string;
if (requiresRefresh) {
const brand = SdkConfig.get().brand;
refreshWarning = value
? _t("Leaving the beta will reload %(brand)s.", { brand })
: _t("Joining the beta will reload %(brand)s.", { brand });
}
let content: ReactNode; let content: ReactNode;
if (busy) { if (busy) {
content = <InlineSpinner />; content = <InlineSpinner />;
@ -137,8 +145,11 @@ const BetaCard = ({ title: titleOverride, featureId }: IProps) => {
{ content } { content }
</AccessibleButton> </AccessibleButton>
</div> </div>
{ disclaimer && <div className="mx_BetaCard_disclaimer"> { refreshWarning && <div className="mx_BetaCard_refreshWarning">
{ disclaimer(value) } { refreshWarning }
</div> }
{ faq && <div className="mx_BetaCard_faq">
{ faq(value) }
</div> } </div> }
</div> </div>
<div className="mx_BetaCard_columns_image_wrapper"> <div className="mx_BetaCard_columns_image_wrapper">

View file

@ -184,7 +184,9 @@ const SpaceContextMenu = ({ space, hideHeader, onFinished, ...props }: IProps) =
iconClassName="mx_SpacePanel_iconPlus" iconClassName="mx_SpacePanel_iconPlus"
label={_t("Video room")} label={_t("Video room")}
onClick={onNewVideoRoomClick} onClick={onNewVideoRoomClick}
/> >
<BetaPill />
</IconizedContextMenuOption>
} }
{ canAddSubSpaces && { canAddSubSpaces &&
<IconizedContextMenuOption <IconizedContextMenuOption

View file

@ -23,6 +23,9 @@ import { CallType } from "matrix-js-sdk/src/webrtc/call";
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { UserTab } from "../dialogs/UserTab";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import RoomHeaderButtons from '../right_panel/RoomHeaderButtons'; import RoomHeaderButtons from '../right_panel/RoomHeaderButtons';
import E2EIcon from './E2EIcon'; import E2EIcon from './E2EIcon';
@ -41,6 +44,7 @@ import { RightPanelPhases } from '../../../stores/right-panel/RightPanelStorePha
import { NotificationStateEvents } from '../../../stores/notifications/NotificationState'; import { NotificationStateEvents } from '../../../stores/notifications/NotificationState';
import RoomContext from "../../../contexts/RoomContext"; import RoomContext from "../../../contexts/RoomContext";
import RoomLiveShareWarning from '../beacon/RoomLiveShareWarning'; import RoomLiveShareWarning from '../beacon/RoomLiveShareWarning';
import { BetaPill } from "../beta/BetaCard";
export interface ISearchInfo { export interface ISearchInfo {
searchTerm: string; searchTerm: string;
@ -272,6 +276,15 @@ export default class RoomHeader extends React.Component<IProps, IState> {
const e2eIcon = this.props.e2eStatus ? <E2EIcon status={this.props.e2eStatus} /> : undefined; const e2eIcon = this.props.e2eStatus ? <E2EIcon status={this.props.e2eStatus} /> : undefined;
const isVideoRoom = SettingsStore.getValue("feature_video_rooms") && this.props.room.isElementVideoRoom();
const viewLabs = () => defaultDispatcher.dispatch({
action: Action.ViewUserSettings,
initialTabId: UserTab.Labs,
});
const betaPill = isVideoRoom ? (
<BetaPill onClick={viewLabs} tooltipTitle={_t("Video rooms are a beta feature")} />
) : null;
return ( return (
<div className="mx_RoomHeader light-panel"> <div className="mx_RoomHeader light-panel">
<div className="mx_RoomHeader_wrapper" aria-owns="mx_RightPanel"> <div className="mx_RoomHeader_wrapper" aria-owns="mx_RightPanel">
@ -280,6 +293,7 @@ export default class RoomHeader extends React.Component<IProps, IState> {
{ name } { name }
{ searchStatus } { searchStatus }
{ topicElement } { topicElement }
{ betaPill }
{ rightRow } { rightRow }
<RoomHeaderButtons room={this.props.room} excludedRightPanelPhaseButtons={this.props.excludedRightPanelPhaseButtons} /> <RoomHeaderButtons room={this.props.room} excludedRightPanelPhaseButtons={this.props.excludedRightPanelPhaseButtons} />
</div> </div>

View file

@ -41,6 +41,7 @@ import IconizedContextMenu, {
IconizedContextMenuOptionList, IconizedContextMenuOptionList,
} from "../context_menus/IconizedContextMenu"; } from "../context_menus/IconizedContextMenu";
import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton"; import AccessibleButton, { ButtonEvent } from "../elements/AccessibleButton";
import { BetaPill } from "../beta/BetaCard";
import SpaceStore from "../../../stores/spaces/SpaceStore"; import SpaceStore from "../../../stores/spaces/SpaceStore";
import { import {
isMetaSpace, isMetaSpace,
@ -238,7 +239,8 @@ const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => {
tooltip={canAddRooms ? undefined tooltip={canAddRooms ? undefined
: _t("You do not have permissions to create new rooms in this space")} : _t("You do not have permissions to create new rooms in this space")}
/> />
{ SettingsStore.getValue("feature_video_rooms") && <IconizedContextMenuOption { SettingsStore.getValue("feature_video_rooms") && (
<IconizedContextMenuOption
label={_t("New video room")} label={_t("New video room")}
iconClassName="mx_RoomList_iconNewVideoRoom" iconClassName="mx_RoomList_iconNewVideoRoom"
onClick={(e) => { onClick={(e) => {
@ -250,7 +252,10 @@ const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => {
disabled={!canAddRooms} disabled={!canAddRooms}
tooltip={canAddRooms ? undefined tooltip={canAddRooms ? undefined
: _t("You do not have permissions to create new rooms in this space")} : _t("You do not have permissions to create new rooms in this space")}
/> } >
<BetaPill />
</IconizedContextMenuOption>
) }
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("Add existing room")} label={_t("Add existing room")}
iconClassName="mx_RoomList_iconAddExistingRoom" iconClassName="mx_RoomList_iconAddExistingRoom"
@ -282,7 +287,8 @@ const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => {
PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e); PosthogTrackers.trackInteraction("WebRoomListRoomsSublistPlusMenuCreateRoomItem", e);
}} }}
/> />
{ SettingsStore.getValue("feature_video_rooms") && <IconizedContextMenuOption { SettingsStore.getValue("feature_video_rooms") && (
<IconizedContextMenuOption
label={_t("New video room")} label={_t("New video room")}
iconClassName="mx_RoomList_iconNewVideoRoom" iconClassName="mx_RoomList_iconNewVideoRoom"
onClick={(e) => { onClick={(e) => {
@ -294,7 +300,10 @@ const UntaggedAuxButton = ({ tabIndex }: IAuxButtonProps) => {
type: RoomType.ElementVideo, type: RoomType.ElementVideo,
}); });
}} }}
/> } >
<BetaPill />
</IconizedContextMenuOption>
) }
</> } </> }
<IconizedContextMenuOption <IconizedContextMenuOption
label={_t("Explore public rooms")} label={_t("Explore public rooms")}

View file

@ -220,7 +220,8 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
closePlusMenu(); closePlusMenu();
}} }}
/> />
{ videoRoomsEnabled && <IconizedContextMenuOption { videoRoomsEnabled && (
<IconizedContextMenuOption
iconClassName="mx_RoomListHeader_iconNewVideoRoom" iconClassName="mx_RoomListHeader_iconNewVideoRoom"
label={_t("New video room")} label={_t("New video room")}
onClick={(e) => { onClick={(e) => {
@ -229,7 +230,10 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
showCreateNewRoom(activeSpace, RoomType.ElementVideo); showCreateNewRoom(activeSpace, RoomType.ElementVideo);
closePlusMenu(); closePlusMenu();
}} }}
/> } >
<BetaPill />
</IconizedContextMenuOption>
) }
</>; </>;
} }
@ -312,7 +316,8 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
closePlusMenu(); closePlusMenu();
}} }}
/> />
{ videoRoomsEnabled && <IconizedContextMenuOption { videoRoomsEnabled && (
<IconizedContextMenuOption
label={_t("New video room")} label={_t("New video room")}
iconClassName="mx_RoomListHeader_iconNewVideoRoom" iconClassName="mx_RoomListHeader_iconNewVideoRoom"
onClick={(e) => { onClick={(e) => {
@ -324,7 +329,10 @@ const RoomListHeader = ({ onVisibilityChange }: IProps) => {
}); });
closePlusMenu(); closePlusMenu();
}} }}
/> } >
<BetaPill />
</IconizedContextMenuOption>
) }
</>; </>;
} }
if (canExploreRooms) { if (canExploreRooms) {

View file

@ -35,6 +35,7 @@ import RoomTopic from "../elements/RoomTopic";
import RoomFacePile from "../elements/RoomFacePile"; import RoomFacePile from "../elements/RoomFacePile";
import RoomAvatar from "../avatars/RoomAvatar"; import RoomAvatar from "../avatars/RoomAvatar";
import MemberAvatar from "../avatars/MemberAvatar"; import MemberAvatar from "../avatars/MemberAvatar";
import { BetaPill } from "../beta/BetaCard";
import RoomInfoLine from "./RoomInfoLine"; import RoomInfoLine from "./RoomInfoLine";
interface IProps { interface IProps {
@ -151,6 +152,7 @@ const RoomPreviewCard: FC<IProps> = ({ room, onJoinButtonClicked, onRejectButton
avatarRow = <> avatarRow = <>
<RoomAvatar room={room} height={50} width={50} viewAvatarOnClick /> <RoomAvatar room={room} height={50} width={50} viewAvatarOnClick />
<div className="mx_RoomPreviewCard_video" /> <div className="mx_RoomPreviewCard_video" />
<BetaPill onClick={viewLabs} tooltipTitle={_t("Video rooms are a beta feature")} />
</>; </>;
} else if (room.isSpaceRoom()) { } else if (room.isSpaceRoom()) {
avatarRow = <RoomAvatar room={room} height={80} width={80} viewAvatarOnClick />; avatarRow = <RoomAvatar room={room} height={80} width={80} viewAvatarOnClick />;

View file

@ -869,6 +869,14 @@
"Encryption": "Encryption", "Encryption": "Encryption",
"Experimental": "Experimental", "Experimental": "Experimental",
"Developer": "Developer", "Developer": "Developer",
"Video rooms": "Video rooms",
"A new way to chat over voice and video in %(brand)s.": "A new way to chat over voice and video in %(brand)s.",
"Video rooms are always-on VoIP channels embedded within a room in %(brand)s.": "Video rooms are always-on VoIP channels embedded within a room in %(brand)s.",
"How can I create a video room?": "How can I create a video room?",
"Use the “+” button in the room section of the left panel.": "Use the “+” button in the room section of the left panel.",
"Can I use text chat alongside the video call?": "Can I use text chat alongside the video call?",
"Yes, the chat timeline is displayed alongside the video.": "Yes, the chat timeline is displayed alongside the video.",
"Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Thank you for trying the beta, please go into as much detail as you can so we can improve it.",
"Let moderators hide messages pending moderation.": "Let moderators hide messages pending moderation.", "Let moderators hide messages pending moderation.": "Let moderators hide messages pending moderation.",
"Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators", "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators": "Report to moderators prototype. In rooms that support moderation, the `report` button will let you report abuse to room moderators",
"Render LaTeX maths in messages": "Render LaTeX maths in messages", "Render LaTeX maths in messages": "Render LaTeX maths in messages",
@ -882,8 +890,6 @@
"How can I leave the beta?": "How can I leave the beta?", "How can I leave the beta?": "How can I leave the beta?",
"To leave, return to this page and use the “%(leaveTheBeta)s” button.": "To leave, return to this page and use the “%(leaveTheBeta)s” button.", "To leave, return to this page and use the “%(leaveTheBeta)s” button.": "To leave, return to this page and use the “%(leaveTheBeta)s” button.",
"Leave the beta": "Leave the beta", "Leave the beta": "Leave the beta",
"Thank you for trying the beta, please go into as much detail as you can so we can improve it.": "Thank you for trying the beta, please go into as much detail as you can so we can improve it.",
"Video rooms (under active development)": "Video rooms (under active development)",
"Render simple counters in room header": "Render simple counters in room header", "Render simple counters in room header": "Render simple counters in room header",
"Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)", "Try out new ways to ignore people (experimental)": "Try out new ways to ignore people (experimental)",
"Support adding custom themes": "Support adding custom themes", "Support adding custom themes": "Support adding custom themes",
@ -1784,6 +1790,7 @@
"Show Widgets": "Show Widgets", "Show Widgets": "Show Widgets",
"Search": "Search", "Search": "Search",
"Invite": "Invite", "Invite": "Invite",
"Video rooms are a beta feature": "Video rooms are a beta feature",
"Video room": "Video room", "Video room": "Video room",
"Public space": "Public space", "Public space": "Public space",
"Public room": "Public room", "Public room": "Public room",
@ -2954,6 +2961,8 @@
"This is a beta feature": "This is a beta feature", "This is a beta feature": "This is a beta feature",
"Click for more info": "Click for more info", "Click for more info": "Click for more info",
"Beta": "Beta", "Beta": "Beta",
"Leaving the beta will reload %(brand)s.": "Leaving the beta will reload %(brand)s.",
"Joining the beta will reload %(brand)s.": "Joining the beta will reload %(brand)s.",
"Join the beta": "Join the beta", "Join the beta": "Join the beta",
"Updated %(humanizedUpdateTime)s": "Updated %(humanizedUpdateTime)s", "Updated %(humanizedUpdateTime)s": "Updated %(humanizedUpdateTime)s",
"Live until %(expiryTime)s": "Live until %(expiryTime)s", "Live until %(expiryTime)s": "Live until %(expiryTime)s",

View file

@ -165,7 +165,7 @@ export interface IBaseSetting<T extends SettingValueType = SettingValueType> {
betaInfo?: { betaInfo?: {
title: string; // _td title: string; // _td
caption: () => ReactNode; caption: () => ReactNode;
disclaimer?: (enabled: boolean) => ReactNode; faq?: (enabled: boolean) => ReactNode;
image?: string; // require(...) image?: string; // require(...)
feedbackSubheading?: string; feedbackSubheading?: string;
feedbackLabel?: string; feedbackLabel?: string;
@ -184,6 +184,42 @@ export interface IFeature extends Omit<IBaseSetting<boolean>, "isFeature"> {
export type ISetting = IBaseSetting | IFeature; export type ISetting = IBaseSetting | IFeature;
export const SETTINGS: {[setting: string]: ISetting} = { export const SETTINGS: {[setting: string]: ISetting} = {
"feature_video_rooms": {
isFeature: true,
labsGroup: LabGroup.Rooms,
displayName: _td("Video rooms"),
supportedLevels: LEVELS_FEATURE,
default: false,
// Reload to ensure that the left panel etc. get remounted
controller: new ReloadOnChangeController(),
betaInfo: {
title: _td("Video rooms"),
caption: () => <>
<p>
{ _t("A new way to chat over voice and video in %(brand)s.", {
brand: SdkConfig.get().brand,
}) }
</p>
<p>
{ _t("Video rooms are always-on VoIP channels embedded within a room in %(brand)s.", {
brand: SdkConfig.get().brand,
}) }
</p>
</>,
faq: () =>
SdkConfig.get().bug_report_endpoint_url && <>
<h4>{ _t("How can I create a video room?") }</h4>
<p>{ _t("Use the “+” button in the room section of the left panel.") }</p>
<h4>{ _t("Can I use text chat alongside the video call?") }</h4>
<p>{ _t("Yes, the chat timeline is displayed alongside the video.") }</p>
</>,
feedbackLabel: "video-room-feedback",
feedbackSubheading: _td("Thank you for trying the beta, " +
"please go into as much detail as you can so we can improve it."),
image: require("../../res/img/betas/video_rooms.png"),
requiresRefresh: true,
},
},
"feature_msc3531_hide_messages_pending_moderation": { "feature_msc3531_hide_messages_pending_moderation": {
isFeature: true, isFeature: true,
labsGroup: LabGroup.Moderation, labsGroup: LabGroup.Moderation,
@ -232,7 +268,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
</a>, </a>,
}) }</p> }) }</p>
</>, </>,
disclaimer: () => faq: () =>
SdkConfig.get().bug_report_endpoint_url && <> SdkConfig.get().bug_report_endpoint_url && <>
<h4>{ _t("How can I start a thread?") }</h4> <h4>{ _t("How can I start a thread?") }</h4>
<p> <p>
@ -255,15 +291,6 @@ export const SETTINGS: {[setting: string]: ISetting} = {
}, },
}, },
"feature_video_rooms": {
isFeature: true,
labsGroup: LabGroup.Rooms,
displayName: _td("Video rooms (under active development)"),
supportedLevels: LEVELS_FEATURE,
default: false,
// Reload to ensure that the left panel etc. get remounted
controller: new ReloadOnChangeController(),
},
"feature_state_counters": { "feature_state_counters": {
isFeature: true, isFeature: true,
labsGroup: LabGroup.Rooms, labsGroup: LabGroup.Rooms,
@ -363,7 +390,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
<p>{ _t("A new, quick way to search spaces and rooms you're in.") }</p> <p>{ _t("A new, quick way to search spaces and rooms you're in.") }</p>
<p>{ _t("This feature is a work in progress, we'd love to hear your feedback.") }</p> <p>{ _t("This feature is a work in progress, we'd love to hear your feedback.") }</p>
</>, </>,
disclaimer: () => <> faq: () => <>
{ SdkConfig.get().bug_report_endpoint_url && <> { SdkConfig.get().bug_report_endpoint_url && <>
<h4>{ _t("How can I give feedback?") }</h4> <h4>{ _t("How can I give feedback?") }</h4>
<p>{ _t("To feedback, join the beta, start a search and click on feedback.") }</p> <p>{ _t("To feedback, join the beta, start a search and click on feedback.") }</p>