mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
fix(desktop): open profiles from avatars (#3751)
## Summary - show profile descriptions in hover cards as a single truncated line - open the profile panel when avatars are clicked across desktop surfaces - make the direct-message intro avatar clickable ## Validation - Desktop static checks - 3,807 desktop tests via pre-push --------- Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
@@ -18,12 +18,10 @@ const rules = [
|
|||||||
// Non-display uses: array windows over pubkey lists, color/initials
|
// Non-display uses: array windows over pubkey lists, color/initials
|
||||||
// derivation where the value is never presented as an identity.
|
// derivation where the value is never presented as an identity.
|
||||||
const overrides = new Set([
|
const overrides = new Set([
|
||||||
// ProfileAvatar fallback label — decorative glyphs inside an avatar disc.
|
|
||||||
"src/features/huddle/components/ParticipantList.tsx:92",
|
|
||||||
// HexAvatar: 6-char badge + hue derivation inside a color-coded disc,
|
// HexAvatar: 6-char badge + hue derivation inside a color-coded disc,
|
||||||
// clearly decorative (paired with a full truncatePubkey aria-label).
|
// clearly decorative (paired with a full truncatePubkey aria-label).
|
||||||
"src/features/huddle/components/ParticipantList.tsx:143",
|
"src/features/huddle/components/ParticipantList.tsx:150",
|
||||||
"src/features/huddle/components/ParticipantList.tsx:144",
|
"src/features/huddle/components/ParticipantList.tsx:151",
|
||||||
// clientId (not a pubkey) sliced in a debug log next to the real thing.
|
// clientId (not a pubkey) sliced in a debug log next to the real thing.
|
||||||
"src/features/channels/readState/readStateManager.ts:338",
|
"src/features/channels/readState/readStateManager.ts:338",
|
||||||
// Array windows (first N pubkeys), not string truncation.
|
// Array windows (first N pubkeys), not string truncation.
|
||||||
|
|||||||
25
desktop/src/app/AppHuddleBar.tsx
Normal file
25
desktop/src/app/AppHuddleBar.tsx
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type * as React from "react";
|
||||||
|
|
||||||
|
import { HuddleBar } from "@/features/huddle";
|
||||||
|
|
||||||
|
import { AppProfilePanelProvider } from "@/app/AppProfilePanelProvider";
|
||||||
|
|
||||||
|
type AppHuddleBarProps = Pick<
|
||||||
|
React.ComponentProps<typeof HuddleBar>,
|
||||||
|
"onOpenThread" | "onVisibilityChange"
|
||||||
|
>;
|
||||||
|
|
||||||
|
export function AppHuddleBar({
|
||||||
|
onOpenThread,
|
||||||
|
onVisibilityChange,
|
||||||
|
}: AppHuddleBarProps) {
|
||||||
|
return (
|
||||||
|
<AppProfilePanelProvider>
|
||||||
|
<HuddleBar
|
||||||
|
className="h-full"
|
||||||
|
onOpenThread={onOpenThread}
|
||||||
|
onVisibilityChange={onVisibilityChange}
|
||||||
|
/>
|
||||||
|
</AppProfilePanelProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
22
desktop/src/app/AppProfilePanelProvider.tsx
Normal file
22
desktop/src/app/AppProfilePanelProvider.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
|
||||||
|
import { ProfilePanelProvider } from "@/shared/context/ProfilePanelContext";
|
||||||
|
|
||||||
|
export function AppProfilePanelProvider({
|
||||||
|
children,
|
||||||
|
}: Readonly<{ children: React.ReactNode }>) {
|
||||||
|
const { goProfile } = useAppNavigation();
|
||||||
|
const handleOpenProfilePanel = React.useCallback(
|
||||||
|
(pubkey: string) => {
|
||||||
|
void goProfile(pubkey);
|
||||||
|
},
|
||||||
|
[goProfile],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ProfilePanelProvider onOpenProfilePanel={handleOpenProfilePanel}>
|
||||||
|
{children}
|
||||||
|
</ProfilePanelProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -63,7 +63,8 @@ import {
|
|||||||
type SettingsSection,
|
type SettingsSection,
|
||||||
isSettingsSection,
|
isSettingsSection,
|
||||||
} from "@/features/settings/ui/SettingsPanels";
|
} from "@/features/settings/ui/SettingsPanels";
|
||||||
import { HuddleBar, HuddleProvider } from "@/features/huddle";
|
import { HuddleProvider } from "@/features/huddle";
|
||||||
|
import { AppHuddleBar } from "@/app/AppHuddleBar";
|
||||||
import { useDueReminderBadgeCount } from "@/features/reminders/hooks";
|
import { useDueReminderBadgeCount } from "@/features/reminders/hooks";
|
||||||
import { RemindMeLaterProvider } from "@/features/reminders/ui/RemindMeLaterProvider";
|
import { RemindMeLaterProvider } from "@/features/reminders/ui/RemindMeLaterProvider";
|
||||||
import { useReminderNotifications } from "@/features/reminders/useReminderNotifications";
|
import { useReminderNotifications } from "@/features/reminders/useReminderNotifications";
|
||||||
@@ -97,7 +98,7 @@ import { SidebarInset, SidebarProvider } from "@/shared/ui/sidebar";
|
|||||||
import { RelayConnectionOverlay } from "@/app/RelayConnectionOverlay";
|
import { RelayConnectionOverlay } from "@/app/RelayConnectionOverlay";
|
||||||
import { useSidebarRelayConnectionCard } from "@/features/sidebar/ui/useSidebarRelayConnectionCard";
|
import { useSidebarRelayConnectionCard } from "@/features/sidebar/ui/useSidebarRelayConnectionCard";
|
||||||
import { AppShellTrayMenu } from "@/app/useAppShellTrayMenu";
|
import { AppShellTrayMenu } from "@/app/useAppShellTrayMenu";
|
||||||
|
import { AppProfilePanelProvider } from "@/app/AppProfilePanelProvider";
|
||||||
const LazySettingsScreen = React.lazy(async () => {
|
const LazySettingsScreen = React.lazy(async () => {
|
||||||
const module = await import("@/features/settings/ui/SettingsScreen");
|
const module = await import("@/features/settings/ui/SettingsScreen");
|
||||||
return { default: module.SettingsScreen };
|
return { default: module.SettingsScreen };
|
||||||
@@ -160,7 +161,6 @@ export function AppShell() {
|
|||||||
? locationSearchSection
|
? locationSearchSection
|
||||||
: DEFAULT_SETTINGS_SECTION;
|
: DEFAULT_SETTINGS_SECTION;
|
||||||
const startupReady = useDeferredStartup();
|
const startupReady = useDeferredStartup();
|
||||||
|
|
||||||
const identityQuery = useIdentityQuery();
|
const identityQuery = useIdentityQuery();
|
||||||
const { mutedChannelIds, muteChannel, unmuteChannel } = useChannelMutes(
|
const { mutedChannelIds, muteChannel, unmuteChannel } = useChannelMutes(
|
||||||
identityQuery.data?.pubkey,
|
identityQuery.data?.pubkey,
|
||||||
@@ -303,7 +303,6 @@ export function AppShell() {
|
|||||||
? (channels.find((channel) => channel.id === targetChannelId) ?? null)
|
? (channels.find((channel) => channel.id === targetChannelId) ?? null)
|
||||||
: null;
|
: null;
|
||||||
}, [channels, managedChannelId, selectedChannelId]);
|
}, [channels, managedChannelId, selectedChannelId]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleChannelNotification,
|
handleChannelNotification,
|
||||||
handleDmNotification,
|
handleDmNotification,
|
||||||
@@ -518,7 +517,6 @@ export function AppShell() {
|
|||||||
},
|
},
|
||||||
[applyAgents, applyCanvas, createChannelMutation, goChannel],
|
[applyAgents, applyCanvas, createChannelMutation, goChannel],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCreateForum = React.useCallback(
|
const handleCreateForum = React.useCallback(
|
||||||
async ({
|
async ({
|
||||||
description,
|
description,
|
||||||
@@ -586,7 +584,6 @@ export function AppShell() {
|
|||||||
},
|
},
|
||||||
[goHome, hideDmMutation, selectedChannelId],
|
[goHome, hideDmMutation, selectedChannelId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleOpenSettings = React.useCallback(
|
const handleOpenSettings = React.useCallback(
|
||||||
(section: SettingsSection = DEFAULT_SETTINGS_SECTION) => {
|
(section: SettingsSection = DEFAULT_SETTINGS_SECTION) => {
|
||||||
setIsChannelManagementOpen(false);
|
setIsChannelManagementOpen(false);
|
||||||
@@ -594,12 +591,10 @@ export function AppShell() {
|
|||||||
},
|
},
|
||||||
[goSettings],
|
[goSettings],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleCloseSettings = React.useCallback(
|
const handleCloseSettings = React.useCallback(
|
||||||
() => closeSettings(),
|
() => closeSettings(),
|
||||||
[closeSettings],
|
[closeSettings],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Section switches rewrite the settings entry rather than stacking one
|
// Section switches rewrite the settings entry rather than stacking one
|
||||||
// history entry per section, so back always exits settings in one step.
|
// history entry per section, so back always exits settings in one step.
|
||||||
const handleSettingsSectionChange = React.useCallback(
|
const handleSettingsSectionChange = React.useCallback(
|
||||||
@@ -620,11 +615,8 @@ export function AppShell() {
|
|||||||
unreadChannelIds,
|
unreadChannelIds,
|
||||||
unreadChannelNotificationCount,
|
unreadChannelNotificationCount,
|
||||||
});
|
});
|
||||||
|
// Dispatch `buzz://message` deep links into the router.
|
||||||
useMessageDeepLinks();
|
useMessageDeepLinks();
|
||||||
const handleOpenNewDm = React.useCallback(
|
|
||||||
() => void goNewMessage(),
|
|
||||||
[goNewMessage],
|
|
||||||
);
|
|
||||||
const handleOpenCreateChannel = React.useCallback(
|
const handleOpenCreateChannel = React.useCallback(
|
||||||
() => setIsCreateChannelOpen(true),
|
() => setIsCreateChannelOpen(true),
|
||||||
[],
|
[],
|
||||||
@@ -657,7 +649,7 @@ export function AppShell() {
|
|||||||
|
|
||||||
if (key === "k" && event.shiftKey) {
|
if (key === "k" && event.shiftKey) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleOpenNewDm();
|
void goNewMessage();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,9 +678,9 @@ export function AppShell() {
|
|||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
handleOpenBrowseChannels,
|
handleOpenBrowseChannels,
|
||||||
handleOpenNewDm,
|
|
||||||
handleOpenCreateChannel,
|
handleOpenCreateChannel,
|
||||||
handleOpenSearch,
|
handleOpenSearch,
|
||||||
|
goNewMessage,
|
||||||
goHome,
|
goHome,
|
||||||
settingsOpen,
|
settingsOpen,
|
||||||
]);
|
]);
|
||||||
@@ -770,216 +762,224 @@ export function AppShell() {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
<SidebarProvider className="min-h-0 flex-1 flex-col overflow-hidden">
|
<SidebarProvider className="min-h-0 flex-1 flex-col overflow-hidden">
|
||||||
{!settingsOpen ? (
|
<AppProfilePanelProvider>
|
||||||
<AppTopChrome
|
{!settingsOpen ? (
|
||||||
canGoBack={canGoBack}
|
<AppTopChrome
|
||||||
canGoForward={canGoForward}
|
canGoBack={canGoBack}
|
||||||
hasCommunityRail={hasCommunityRail}
|
canGoForward={canGoForward}
|
||||||
onGoBack={goBack}
|
hasCommunityRail={hasCommunityRail}
|
||||||
onGoForward={goForward}
|
onGoBack={goBack}
|
||||||
/>
|
onGoForward={goForward}
|
||||||
) : null}
|
/>
|
||||||
{settingsOpen ? (
|
) : null}
|
||||||
<div className="flex min-h-0 flex-1 overflow-hidden">
|
{settingsOpen ? (
|
||||||
<React.Suspense fallback={null}>
|
<div className="flex min-h-0 flex-1 overflow-hidden">
|
||||||
<LazySettingsScreen
|
<React.Suspense fallback={null}>
|
||||||
|
<LazySettingsScreen
|
||||||
|
currentPubkey={identityQuery.data?.pubkey}
|
||||||
|
fallbackDisplayName={
|
||||||
|
identityQuery.data?.displayName
|
||||||
|
}
|
||||||
|
isUpdatingDesktopNotifications={
|
||||||
|
notificationSettings.isUpdatingDesktopEnabled
|
||||||
|
}
|
||||||
|
notificationErrorMessage={
|
||||||
|
notificationSettings.errorMessage
|
||||||
|
}
|
||||||
|
notificationPermission={
|
||||||
|
notificationSettings.permission
|
||||||
|
}
|
||||||
|
notificationSettings={
|
||||||
|
notificationSettings.settings
|
||||||
|
}
|
||||||
|
onClose={handleCloseSettings}
|
||||||
|
onSectionChange={handleSettingsSectionChange}
|
||||||
|
onSetDesktopNotificationsEnabled={
|
||||||
|
notificationSettings.setDesktopEnabled
|
||||||
|
}
|
||||||
|
onSetHomeBadgeEnabled={
|
||||||
|
notificationSettings.setHomeBadgeEnabled
|
||||||
|
}
|
||||||
|
onSetSlotAlertsEnabled={
|
||||||
|
notificationSettings.setSlotAlertsEnabled
|
||||||
|
}
|
||||||
|
onSetNotifyWhileViewing={
|
||||||
|
notificationSettings.setNotifyWhileViewing
|
||||||
|
}
|
||||||
|
onSetAllSlotAlertsEnabled={
|
||||||
|
notificationSettings.setAllSlotAlertsEnabled
|
||||||
|
}
|
||||||
|
onSetSoundForSlot={
|
||||||
|
notificationSettings.setSoundForSlot
|
||||||
|
}
|
||||||
|
section={settingsSection}
|
||||||
|
/>
|
||||||
|
</React.Suspense>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex min-h-0 flex-1 overflow-hidden">
|
||||||
|
<AppSidebar
|
||||||
|
activeCommunity={communitiesHook.activeCommunity}
|
||||||
|
channels={sidebarChannels}
|
||||||
currentPubkey={identityQuery.data?.pubkey}
|
currentPubkey={identityQuery.data?.pubkey}
|
||||||
|
errorMessage={channelsErrorMessage}
|
||||||
fallbackDisplayName={
|
fallbackDisplayName={
|
||||||
identityQuery.data?.displayName
|
identityQuery.data?.displayName
|
||||||
}
|
}
|
||||||
isUpdatingDesktopNotifications={
|
homeBadgeCount={homeBadgeCount + dueReminderBadge}
|
||||||
notificationSettings.isUpdatingDesktopEnabled
|
addCommunityPrefill={addCommunityDialog.prefill}
|
||||||
}
|
isAddCommunityOpen={addCommunityDialog.open}
|
||||||
notificationErrorMessage={
|
relayConnectionCard={relayConnectionCard}
|
||||||
notificationSettings.errorMessage
|
isCreatingChannel={createChannelMutation.isPending}
|
||||||
}
|
isCreatingForum={createForumMutation.isPending}
|
||||||
notificationPermission={
|
isLoading={channelsQuery.isLoading}
|
||||||
notificationSettings.permission
|
isCreateChannelOpen={isCreateChannelOpen}
|
||||||
}
|
isPresencePending={presenceSession.isPending}
|
||||||
notificationSettings={notificationSettings.settings}
|
onAddCommunity={(community) => {
|
||||||
onClose={handleCloseSettings}
|
const id = communitiesHook.addCommunity({
|
||||||
onSectionChange={handleSettingsSectionChange}
|
...community,
|
||||||
onSetDesktopNotificationsEnabled={
|
pubkey:
|
||||||
notificationSettings.setDesktopEnabled
|
community.pubkey ??
|
||||||
}
|
identityQuery.data?.pubkey,
|
||||||
onSetHomeBadgeEnabled={
|
|
||||||
notificationSettings.setHomeBadgeEnabled
|
|
||||||
}
|
|
||||||
onSetSlotAlertsEnabled={
|
|
||||||
notificationSettings.setSlotAlertsEnabled
|
|
||||||
}
|
|
||||||
onSetNotifyWhileViewing={
|
|
||||||
notificationSettings.setNotifyWhileViewing
|
|
||||||
}
|
|
||||||
onSetAllSlotAlertsEnabled={
|
|
||||||
notificationSettings.setAllSlotAlertsEnabled
|
|
||||||
}
|
|
||||||
onSetSoundForSlot={
|
|
||||||
notificationSettings.setSoundForSlot
|
|
||||||
}
|
|
||||||
section={settingsSection}
|
|
||||||
/>
|
|
||||||
</React.Suspense>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className="flex min-h-0 flex-1 overflow-hidden">
|
|
||||||
<AppSidebar
|
|
||||||
activeCommunity={communitiesHook.activeCommunity}
|
|
||||||
channels={sidebarChannels}
|
|
||||||
currentPubkey={identityQuery.data?.pubkey}
|
|
||||||
errorMessage={channelsErrorMessage}
|
|
||||||
fallbackDisplayName={identityQuery.data?.displayName}
|
|
||||||
homeBadgeCount={homeBadgeCount + dueReminderBadge}
|
|
||||||
addCommunityPrefill={addCommunityDialog.prefill}
|
|
||||||
isAddCommunityOpen={addCommunityDialog.open}
|
|
||||||
relayConnectionCard={relayConnectionCard}
|
|
||||||
isCreatingChannel={createChannelMutation.isPending}
|
|
||||||
isCreatingForum={createForumMutation.isPending}
|
|
||||||
isLoading={channelsQuery.isLoading}
|
|
||||||
isCreateChannelOpen={isCreateChannelOpen}
|
|
||||||
isPresencePending={presenceSession.isPending}
|
|
||||||
onAddCommunity={(community) => {
|
|
||||||
const id = communitiesHook.addCommunity({
|
|
||||||
...community,
|
|
||||||
pubkey:
|
|
||||||
community.pubkey ?? identityQuery.data?.pubkey,
|
|
||||||
});
|
|
||||||
handleSwitchCommunity(id);
|
|
||||||
}}
|
|
||||||
onAddCommunityOpenChange={
|
|
||||||
addCommunityDialog.onOpenChange
|
|
||||||
}
|
|
||||||
onNewMessage={handleOpenNewDm}
|
|
||||||
onBackgroundClick={requestFocusedThreadClose}
|
|
||||||
onCreateChannelOpenChange={setIsCreateChannelOpen}
|
|
||||||
onOpenAddCommunity={addCommunityDialog.openDialog}
|
|
||||||
onSendFeedback={() => setIsSendFeedbackOpen(true)}
|
|
||||||
onUpdateCommunity={communitiesHook.updateCommunity}
|
|
||||||
onRemoveCommunity={(id) =>
|
|
||||||
void handleRemoveCommunity(id)
|
|
||||||
}
|
|
||||||
onSwitchCommunity={handleSwitchCommunity}
|
|
||||||
onCreateAgent={() => requestOpenCreateAgent()}
|
|
||||||
selfPresenceStatus={presenceSession.currentStatus}
|
|
||||||
communities={communitiesHook.communities}
|
|
||||||
onCreateChannel={handleCreateChannel}
|
|
||||||
onCreateForum={handleCreateForum}
|
|
||||||
onHideDm={handleHideDm}
|
|
||||||
onMarkAllChannelsRead={markAllChannelsRead}
|
|
||||||
onMarkChannelRead={markChannelRead}
|
|
||||||
onMarkChannelUnread={markChannelUnread}
|
|
||||||
onBrowseChannels={handleOpenBrowseChannels}
|
|
||||||
onOpenDm={async ({ pubkeys }) => {
|
|
||||||
const directMessage =
|
|
||||||
await openDmMutation.mutateAsync({
|
|
||||||
pubkeys,
|
|
||||||
});
|
});
|
||||||
await goChannel(directMessage.id);
|
handleSwitchCommunity(id);
|
||||||
}}
|
}}
|
||||||
onSelectAgents={() => void goAgents()}
|
onAddCommunityOpenChange={
|
||||||
onSelectChannel={(channelId) =>
|
addCommunityDialog.onOpenChange
|
||||||
void goChannel(channelId)
|
}
|
||||||
}
|
onNewMessage={goNewMessage}
|
||||||
onOpenSearchResult={handleOpenSearchResult}
|
onBackgroundClick={requestFocusedThreadClose}
|
||||||
searchChannels={channels}
|
onCreateChannelOpenChange={setIsCreateChannelOpen}
|
||||||
searchFocusRequest={searchFocusRequest}
|
onOpenAddCommunity={addCommunityDialog.openDialog}
|
||||||
onSelectHome={() => void goHome()}
|
onSendFeedback={() => setIsSendFeedbackOpen(true)}
|
||||||
onSelectProjects={() => void goProjects()}
|
onUpdateCommunity={communitiesHook.updateCommunity}
|
||||||
onSelectPulse={() => void goPulse()}
|
onRemoveCommunity={(id) =>
|
||||||
onSelectSettings={handleOpenSettings}
|
void handleRemoveCommunity(id)
|
||||||
onSelectWorkflows={() => void goWorkflows()}
|
}
|
||||||
onSetPresenceStatus={(status) =>
|
onSwitchCommunity={handleSwitchCommunity}
|
||||||
presenceSession.setStatus(status)
|
onCreateAgent={() => requestOpenCreateAgent()}
|
||||||
}
|
selfPresenceStatus={presenceSession.currentStatus}
|
||||||
onSetUserStatus={(text, emoji) =>
|
communities={communitiesHook.communities}
|
||||||
setUserStatusMutation.mutate({ text, emoji })
|
onCreateChannel={handleCreateChannel}
|
||||||
}
|
onCreateForum={handleCreateForum}
|
||||||
onClearUserStatus={() =>
|
onHideDm={handleHideDm}
|
||||||
setUserStatusMutation.mutate({
|
onMarkAllChannelsRead={markAllChannelsRead}
|
||||||
text: "",
|
onMarkChannelRead={markChannelRead}
|
||||||
emoji: "",
|
onMarkChannelUnread={markChannelUnread}
|
||||||
})
|
onBrowseChannels={handleOpenBrowseChannels}
|
||||||
}
|
onOpenDm={async ({ pubkeys }) => {
|
||||||
profile={profileQuery.data}
|
const directMessage =
|
||||||
selfUserStatus={
|
await openDmMutation.mutateAsync({
|
||||||
deferredPubkey
|
pubkeys,
|
||||||
? (selfStatusQuery.data?.[
|
});
|
||||||
deferredPubkey.toLowerCase()
|
await goChannel(directMessage.id);
|
||||||
] ?? undefined)
|
}}
|
||||||
: undefined
|
onSelectAgents={() => void goAgents()}
|
||||||
}
|
onSelectChannel={(channelId) =>
|
||||||
selectedChannelId={selectedChannelId}
|
void goChannel(channelId)
|
||||||
selectedView={selectedView}
|
}
|
||||||
unreadChannelIds={unreadChannelIds}
|
onOpenSearchResult={handleOpenSearchResult}
|
||||||
unreadChannelCounts={unreadChannelCounts}
|
searchChannels={channels}
|
||||||
mutedChannelIds={mutedChannelIds}
|
searchFocusRequest={searchFocusRequest}
|
||||||
onMuteChannel={muteChannel}
|
onSelectHome={() => void goHome()}
|
||||||
onUnmuteChannel={unmuteChannel}
|
onSelectProjects={() => void goProjects()}
|
||||||
starredChannelIds={starredChannelIds}
|
onSelectPulse={() => void goPulse()}
|
||||||
onStarChannel={starChannel}
|
onSelectSettings={handleOpenSettings}
|
||||||
onUnstarChannel={unstarChannel}
|
onSelectWorkflows={() => void goWorkflows()}
|
||||||
/>
|
onSetPresenceStatus={(status) =>
|
||||||
<MainInsetProvider mainInsetRef={mainInsetRef}>
|
presenceSession.setStatus(status)
|
||||||
<SidebarInset
|
}
|
||||||
ref={mainInsetRef}
|
onSetUserStatus={(text, emoji) =>
|
||||||
className="isolate min-h-0 min-w-0 overflow-hidden bg-sidebar"
|
setUserStatusMutation.mutate({ text, emoji })
|
||||||
data-buzz-glass-inset
|
}
|
||||||
data-buzz-shadow-viewport
|
onClearUserStatus={() =>
|
||||||
style={chromeCssVarDefaults as React.CSSProperties}
|
setUserStatusMutation.mutate({
|
||||||
>
|
text: "",
|
||||||
<BuzzTheme.ContentSurface>
|
emoji: "",
|
||||||
<Outlet />
|
})
|
||||||
</BuzzTheme.ContentSurface>
|
}
|
||||||
</SidebarInset>
|
profile={profileQuery.data}
|
||||||
</MainInsetProvider>
|
selfUserStatus={
|
||||||
<RelayConnectionOverlay
|
deferredPubkey
|
||||||
card={relayConnectionCard}
|
? (selfStatusQuery.data?.[
|
||||||
errorMessage={channelsErrorMessage}
|
deferredPubkey.toLowerCase()
|
||||||
hasCommunityRail={hasCommunityRail}
|
] ?? undefined)
|
||||||
isHuddleDrawerOpen={isHuddleDrawerOpen}
|
: undefined
|
||||||
/>
|
}
|
||||||
</div>
|
selectedChannelId={selectedChannelId}
|
||||||
)}
|
selectedView={selectedView}
|
||||||
<RequestedAgentCreateDialogs />
|
unreadChannelIds={unreadChannelIds}
|
||||||
<AgentManagementDialogs />
|
unreadChannelCounts={unreadChannelCounts}
|
||||||
<AppShellOverlays
|
mutedChannelIds={mutedChannelIds}
|
||||||
activeChannel={managedChannel}
|
onMuteChannel={muteChannel}
|
||||||
browseDialogType={browseDialogType}
|
onUnmuteChannel={unmuteChannel}
|
||||||
channels={channels}
|
starredChannelIds={starredChannelIds}
|
||||||
currentPubkey={identityQuery.data?.pubkey}
|
onStarChannel={starChannel}
|
||||||
isChannelManagementOpen={isChannelManagementOpen}
|
onUnstarChannel={unstarChannel}
|
||||||
isCreatingBrowseChannel={
|
/>
|
||||||
createChannelMutation.isPending ||
|
<MainInsetProvider mainInsetRef={mainInsetRef}>
|
||||||
createForumMutation.isPending
|
<SidebarInset
|
||||||
}
|
ref={mainInsetRef}
|
||||||
onBrowseChannelJoin={handleBrowseChannelJoin}
|
className="isolate min-h-0 min-w-0 overflow-hidden bg-sidebar"
|
||||||
onBrowseChannelCreate={handleBrowseChannelCreate}
|
data-buzz-glass-inset
|
||||||
onBrowseDialogOpenChange={handleBrowseDialogOpenChange}
|
data-buzz-shadow-viewport
|
||||||
onChannelManagementOpenChange={(open) => {
|
style={
|
||||||
setIsChannelManagementOpen(open);
|
chromeCssVarDefaults as React.CSSProperties
|
||||||
if (!open) {
|
}
|
||||||
setManagedChannelId(null);
|
>
|
||||||
|
<BuzzTheme.ContentSurface>
|
||||||
|
<Outlet />
|
||||||
|
</BuzzTheme.ContentSurface>
|
||||||
|
</SidebarInset>
|
||||||
|
</MainInsetProvider>
|
||||||
|
<RelayConnectionOverlay
|
||||||
|
card={relayConnectionCard}
|
||||||
|
errorMessage={channelsErrorMessage}
|
||||||
|
hasCommunityRail={hasCommunityRail}
|
||||||
|
isHuddleDrawerOpen={isHuddleDrawerOpen}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<RequestedAgentCreateDialogs />
|
||||||
|
<AgentManagementDialogs />
|
||||||
|
<AppShellOverlays
|
||||||
|
activeChannel={managedChannel}
|
||||||
|
browseDialogType={browseDialogType}
|
||||||
|
channels={channels}
|
||||||
|
currentPubkey={identityQuery.data?.pubkey}
|
||||||
|
isChannelManagementOpen={isChannelManagementOpen}
|
||||||
|
isCreatingBrowseChannel={
|
||||||
|
createChannelMutation.isPending ||
|
||||||
|
createForumMutation.isPending
|
||||||
}
|
}
|
||||||
}}
|
onBrowseChannelJoin={handleBrowseChannelJoin}
|
||||||
onDeleteActiveChannel={() => {
|
onBrowseChannelCreate={handleBrowseChannelCreate}
|
||||||
setIsChannelManagementOpen(false);
|
onBrowseDialogOpenChange={handleBrowseDialogOpenChange}
|
||||||
setManagedChannelId(null);
|
onChannelManagementOpenChange={(open) => {
|
||||||
void goHome({ replace: true });
|
setIsChannelManagementOpen(open);
|
||||||
}}
|
if (!open) {
|
||||||
onSelectChannel={(channelId) => {
|
setManagedChannelId(null);
|
||||||
void goChannel(channelId);
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
onDeleteActiveChannel={() => {
|
||||||
<SendFeedbackController
|
setIsChannelManagementOpen(false);
|
||||||
onOpenChange={setIsSendFeedbackOpen}
|
setManagedChannelId(null);
|
||||||
open={isSendFeedbackOpen}
|
void goHome({ replace: true });
|
||||||
/>
|
}}
|
||||||
|
onSelectChannel={(channelId) => {
|
||||||
|
void goChannel(channelId);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SendFeedbackController
|
||||||
|
onOpenChange={setIsSendFeedbackOpen}
|
||||||
|
open={isSendFeedbackOpen}
|
||||||
|
/>
|
||||||
|
</AppProfilePanelProvider>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute inset-x-0 bottom-0 z-0 h-(--buzz-huddle-drawer-height)">
|
<div className="absolute inset-x-0 bottom-0 z-0 h-(--buzz-huddle-drawer-height)">
|
||||||
<HuddleBar
|
<AppHuddleBar
|
||||||
className="h-full"
|
|
||||||
onOpenThread={(channelId, messageId) => {
|
onOpenThread={(channelId, messageId) => {
|
||||||
void goChannel(channelId, {
|
void goChannel(channelId, {
|
||||||
messageId,
|
messageId,
|
||||||
|
|||||||
@@ -79,6 +79,18 @@ export function useAppNavigation() {
|
|||||||
[commitNavigation],
|
[commitNavigation],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const goProfile = React.useCallback(
|
||||||
|
(pubkey: string, behavior?: NavigationBehavior) =>
|
||||||
|
commitNavigation(
|
||||||
|
{
|
||||||
|
to: "/pulse",
|
||||||
|
search: { profile: pubkey },
|
||||||
|
},
|
||||||
|
behavior,
|
||||||
|
),
|
||||||
|
[commitNavigation],
|
||||||
|
);
|
||||||
|
|
||||||
const goProjects = React.useCallback(
|
const goProjects = React.useCallback(
|
||||||
(behavior?: NavigationBehavior) =>
|
(behavior?: NavigationBehavior) =>
|
||||||
commitNavigation(
|
commitNavigation(
|
||||||
@@ -303,6 +315,7 @@ export function useAppNavigation() {
|
|||||||
goProject,
|
goProject,
|
||||||
goProjects,
|
goProjects,
|
||||||
goPulse,
|
goPulse,
|
||||||
|
goProfile,
|
||||||
goSettings,
|
goSettings,
|
||||||
goWorkflow,
|
goWorkflow,
|
||||||
goWorkflows,
|
goWorkflows,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
ProfileAvatarWithStatus,
|
ProfileAvatarWithStatus,
|
||||||
scaleProfileAvatarStatusGeometry,
|
scaleProfileAvatarStatusGeometry,
|
||||||
} from "@/features/profile/ui/ProfileAvatarWithStatus";
|
} from "@/features/profile/ui/ProfileAvatarWithStatus";
|
||||||
|
import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover";
|
||||||
import { Button } from "@/shared/ui/button";
|
import { Button } from "@/shared/ui/button";
|
||||||
import type { Channel, PresenceStatus } from "@/shared/api/types";
|
import type { Channel, PresenceStatus } from "@/shared/api/types";
|
||||||
import { UserAvatar } from "@/shared/ui/UserAvatar";
|
import { UserAvatar } from "@/shared/ui/UserAvatar";
|
||||||
@@ -65,6 +66,7 @@ export function ChannelScreenHeader({
|
|||||||
const isGroupDm =
|
const isGroupDm =
|
||||||
activeChannel?.channelType === "dm" &&
|
activeChannel?.channelType === "dm" &&
|
||||||
activeDmHeaderParticipants.length > 1;
|
activeDmHeaderParticipants.length > 1;
|
||||||
|
const activeDmParticipant = activeDmHeaderParticipants[0] ?? null;
|
||||||
const showJoinButton =
|
const showJoinButton =
|
||||||
activeChannel !== null &&
|
activeChannel !== null &&
|
||||||
!activeChannel.isMember &&
|
!activeChannel.isMember &&
|
||||||
@@ -113,6 +115,25 @@ export function ChannelScreenHeader({
|
|||||||
<DmHeaderParticipantStack
|
<DmHeaderParticipantStack
|
||||||
participants={activeDmHeaderParticipants}
|
participants={activeDmHeaderParticipants}
|
||||||
/>
|
/>
|
||||||
|
) : activeDmParticipant ? (
|
||||||
|
<UserProfilePopover
|
||||||
|
pubkey={activeDmParticipant.pubkey}
|
||||||
|
triggerAriaLabel={`Open profile for ${activeChannelTitle}`}
|
||||||
|
triggerElement="span"
|
||||||
|
>
|
||||||
|
<ProfileAvatarWithStatus
|
||||||
|
avatarClassName="text-xs"
|
||||||
|
avatarUrl={activeDmAvatarUrl}
|
||||||
|
className="mr-1.5 h-8 w-8"
|
||||||
|
geometry={DM_HEADER_AVATAR_STATUS_GEOMETRY}
|
||||||
|
iconClassName="h-4 w-4"
|
||||||
|
label={activeChannelTitle}
|
||||||
|
size={DM_HEADER_AVATAR_SIZE}
|
||||||
|
status={activeDmPresenceStatus ?? "offline"}
|
||||||
|
statusTestId="chat-presence-badge"
|
||||||
|
testId="chat-header-dm-avatar"
|
||||||
|
/>
|
||||||
|
</UserProfilePopover>
|
||||||
) : (
|
) : (
|
||||||
<ProfileAvatarWithStatus
|
<ProfileAvatarWithStatus
|
||||||
avatarClassName="text-xs"
|
avatarClassName="text-xs"
|
||||||
@@ -152,31 +173,36 @@ function DmHeaderParticipantStack({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
|
||||||
className="mr-1.5 flex shrink-0 items-center"
|
className="mr-1.5 flex shrink-0 items-center"
|
||||||
data-testid="chat-header-dm-avatar-stack"
|
data-testid="chat-header-dm-avatar-stack"
|
||||||
>
|
>
|
||||||
{visibleParticipants.map((participant, index) => (
|
{visibleParticipants.map((participant, index) => (
|
||||||
<div
|
<UserProfilePopover
|
||||||
className={index > 0 ? "-ml-2" : ""}
|
|
||||||
data-testid="chat-header-dm-avatar-stack-participant"
|
|
||||||
key={participant.pubkey}
|
key={participant.pubkey}
|
||||||
style={{
|
pubkey={participant.pubkey}
|
||||||
zIndex: index + 1,
|
triggerAriaLabel={`Open profile for ${participant.displayName}`}
|
||||||
...(index < stackItemCount - 1 && {
|
triggerElement="span"
|
||||||
mask: "radial-gradient(circle 18px at calc(100% + 4px) 50%, transparent 99%, #fff 100%)",
|
|
||||||
WebkitMask:
|
|
||||||
"radial-gradient(circle 18px at calc(100% + 4px) 50%, transparent 99%, #fff 100%)",
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<UserAvatar
|
<span
|
||||||
avatarUrl={participant.avatarUrl}
|
className={index > 0 ? "-ml-2" : ""}
|
||||||
className="h-8 w-8 text-xs"
|
data-testid="chat-header-dm-avatar-stack-participant"
|
||||||
displayName={participant.displayName}
|
style={{
|
||||||
size="sm"
|
zIndex: index + 1,
|
||||||
/>
|
...(index < stackItemCount - 1 && {
|
||||||
</div>
|
mask: "radial-gradient(circle 18px at calc(100% + 4px) 50%, transparent 99%, #fff 100%)",
|
||||||
|
WebkitMask:
|
||||||
|
"radial-gradient(circle 18px at calc(100% + 4px) 50%, transparent 99%, #fff 100%)",
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserAvatar
|
||||||
|
avatarUrl={participant.avatarUrl}
|
||||||
|
className="h-8 w-8 text-xs"
|
||||||
|
displayName={participant.displayName}
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</UserProfilePopover>
|
||||||
))}
|
))}
|
||||||
{hiddenCount > 0 ? (
|
{hiddenCount > 0 ? (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
} from "@/features/community-members/hooks";
|
} from "@/features/community-members/hooks";
|
||||||
import { useUsersBatchQuery } from "@/features/profile/hooks";
|
import { useUsersBatchQuery } from "@/features/profile/hooks";
|
||||||
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
|
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
|
||||||
|
import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover";
|
||||||
import { SettingsSectionHeader } from "@/features/settings/ui/SettingsSectionHeader";
|
import { SettingsSectionHeader } from "@/features/settings/ui/SettingsSectionHeader";
|
||||||
import type {
|
import type {
|
||||||
RelayMember,
|
RelayMember,
|
||||||
@@ -129,11 +130,17 @@ function RelayMemberRow({
|
|||||||
className="group/member flex min-h-14 items-center gap-3 px-1 py-2.5"
|
className="group/member flex min-h-14 items-center gap-3 px-1 py-2.5"
|
||||||
data-testid={`relay-member-row-${member.pubkey}`}
|
data-testid={`relay-member-row-${member.pubkey}`}
|
||||||
>
|
>
|
||||||
<ProfileAvatar
|
<UserProfilePopover
|
||||||
avatarUrl={profile?.avatarUrl ?? null}
|
pubkey={member.pubkey}
|
||||||
className="h-9 w-9 text-xs shadow-none"
|
triggerAriaLabel={`Open profile for ${displayName}`}
|
||||||
label={displayName}
|
triggerElement="span"
|
||||||
/>
|
>
|
||||||
|
<ProfileAvatar
|
||||||
|
avatarUrl={profile?.avatarUrl ?? null}
|
||||||
|
className="h-9 w-9 text-xs shadow-none"
|
||||||
|
label={displayName}
|
||||||
|
/>
|
||||||
|
</UserProfilePopover>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex min-w-0 items-center gap-1.5 text-sm font-medium">
|
<div className="flex min-w-0 items-center gap-1.5 text-sm font-medium">
|
||||||
<HoverMemberIdentity
|
<HoverMemberIdentity
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import * as React from "react";
|
|||||||
|
|
||||||
import { useUsersBatchQuery } from "@/features/profile/hooks";
|
import { useUsersBatchQuery } from "@/features/profile/hooks";
|
||||||
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
|
import { ProfileAvatar } from "@/features/profile/ui/ProfileAvatar";
|
||||||
|
import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover";
|
||||||
import { cn } from "@/shared/lib/cn";
|
import { cn } from "@/shared/lib/cn";
|
||||||
import { Button } from "@/shared/ui/button";
|
import { Button } from "@/shared/ui/button";
|
||||||
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
|
import { Popover, PopoverContent, PopoverTrigger } from "@/shared/ui/popover";
|
||||||
@@ -86,19 +87,25 @@ export function HuddleParticipantsControl({
|
|||||||
className="flex min-w-0 items-center gap-2 rounded-md px-2 py-1.5"
|
className="flex min-w-0 items-center gap-2 rounded-md px-2 py-1.5"
|
||||||
key={pubkey}
|
key={pubkey}
|
||||||
>
|
>
|
||||||
{profile?.displayName || profile?.avatarUrl ? (
|
<UserProfilePopover
|
||||||
<ProfileAvatar
|
pubkey={pubkey}
|
||||||
avatarUrl={profile.avatarUrl ?? null}
|
triggerAriaLabel={`Open profile for ${displayName}`}
|
||||||
label={profile.displayName || pubkey.slice(0, 6)}
|
triggerElement="span"
|
||||||
className={cn(
|
>
|
||||||
"h-8 w-8 rounded-full text-2xs",
|
{profile?.displayName || profile?.avatarUrl ? (
|
||||||
isActive &&
|
<ProfileAvatar
|
||||||
"ring-2 ring-green-500 ring-offset-1 ring-offset-background",
|
avatarUrl={profile.avatarUrl ?? null}
|
||||||
)}
|
label={profile.displayName || truncatePubkey(pubkey)}
|
||||||
/>
|
className={cn(
|
||||||
) : (
|
"h-8 w-8 rounded-full text-2xs",
|
||||||
<HexAvatar pubkey={pubkey} isActive={isActive} size="lg" />
|
isActive &&
|
||||||
)}
|
"ring-2 ring-green-500 ring-offset-1 ring-offset-background",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<HexAvatar pubkey={pubkey} isActive={isActive} size="lg" />
|
||||||
|
)}
|
||||||
|
</UserProfilePopover>
|
||||||
|
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="truncate text-sm font-medium">
|
<div className="truncate text-sm font-medium">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getDmParticipantPreview } from "@/features/channels/lib/dmParticipantDisplay";
|
import { getDmParticipantPreview } from "@/features/channels/lib/dmParticipantDisplay";
|
||||||
|
import { UserProfilePopover } from "@/features/profile/ui/UserProfilePopover";
|
||||||
import { UserAvatar } from "@/shared/ui/UserAvatar";
|
import { UserAvatar } from "@/shared/ui/UserAvatar";
|
||||||
|
|
||||||
export type DirectMessageIntroParticipant = {
|
export type DirectMessageIntroParticipant = {
|
||||||
@@ -18,31 +19,36 @@ export function DirectMessageIntroAvatarStack({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
aria-hidden="true"
|
|
||||||
className="flex shrink-0 items-center"
|
className="flex shrink-0 items-center"
|
||||||
data-testid="message-dm-intro-avatar-stack"
|
data-testid="message-dm-intro-avatar-stack"
|
||||||
>
|
>
|
||||||
{visibleParticipants.map((participant, index) => (
|
{visibleParticipants.map((participant, index) => (
|
||||||
<div
|
<UserProfilePopover
|
||||||
className={index > 0 ? "-ml-5" : ""}
|
|
||||||
data-testid="message-dm-intro-avatar-stack-participant"
|
|
||||||
key={participant.pubkey}
|
key={participant.pubkey}
|
||||||
style={{
|
pubkey={participant.pubkey}
|
||||||
zIndex: index + 1,
|
triggerAriaLabel={`Open profile for ${participant.displayName}`}
|
||||||
...(index < stackItemCount - 1 && {
|
triggerElement="span"
|
||||||
mask: "radial-gradient(circle 34px at calc(100% + 10px) 50%, transparent 99%, #fff 100%)",
|
|
||||||
WebkitMask:
|
|
||||||
"radial-gradient(circle 34px at calc(100% + 10px) 50%, transparent 99%, #fff 100%)",
|
|
||||||
}),
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<UserAvatar
|
<span
|
||||||
avatarUrl={participant.avatarUrl}
|
className={index > 0 ? "-ml-5" : ""}
|
||||||
className="h-[60px] w-[60px] text-base"
|
data-testid="message-dm-intro-avatar-stack-participant"
|
||||||
displayName={participant.displayName}
|
style={{
|
||||||
size="md"
|
zIndex: index + 1,
|
||||||
/>
|
...(index < stackItemCount - 1 && {
|
||||||
</div>
|
mask: "radial-gradient(circle 34px at calc(100% + 10px) 50%, transparent 99%, #fff 100%)",
|
||||||
|
WebkitMask:
|
||||||
|
"radial-gradient(circle 34px at calc(100% + 10px) 50%, transparent 99%, #fff 100%)",
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UserAvatar
|
||||||
|
avatarUrl={participant.avatarUrl}
|
||||||
|
className="h-[60px] w-[60px] text-base"
|
||||||
|
displayName={participant.displayName}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</UserProfilePopover>
|
||||||
))}
|
))}
|
||||||
{hiddenCount > 0 ? (
|
{hiddenCount > 0 ? (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { expect, test } from "@playwright/test";
|
import { expect, test } from "@playwright/test";
|
||||||
|
|
||||||
import { waitForAnimations } from "../helpers/animations";
|
import { waitForAnimations } from "../helpers/animations";
|
||||||
import { installMockBridge } from "../helpers/bridge";
|
import { TEST_IDENTITIES, installMockBridge } from "../helpers/bridge";
|
||||||
import { openSettings } from "../helpers/settings";
|
import { openSettings } from "../helpers/settings";
|
||||||
|
|
||||||
const OUTDIR = "test-results/invites-settings";
|
const OUTDIR = "test-results/invites-settings";
|
||||||
@@ -36,6 +36,15 @@ test.beforeEach(async ({ page }, testInfo) => {
|
|||||||
await openSettings(page, "community-members");
|
await openSettings(page, "community-members");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("opens a profile from a community member avatar", async ({ page }) => {
|
||||||
|
await page.getByRole("button", { name: "Open profile for alice" }).click();
|
||||||
|
|
||||||
|
await expect(page).toHaveURL(
|
||||||
|
new RegExp(`/pulse\\?profile=${TEST_IDENTITIES.alice.pubkey}$`),
|
||||||
|
);
|
||||||
|
await expect(page.getByTestId("user-profile-panel")).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test("capture: consolidated invites settings", async ({ page }) => {
|
test("capture: consolidated invites settings", async ({ page }) => {
|
||||||
const panel = page.getByTestId("settings-panel-community-members");
|
const panel = page.getByTestId("settings-panel-community-members");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user