Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions components/auth/SeizeConnectContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
setActiveWalletAccount,
WALLET_ACCOUNTS_UPDATED_EVENT,
} from "@/services/auth/auth.utils";
import { useConnectedAccountsUnreadNotifications } from "@/hooks/useConnectedAccountsUnreadNotifications";
import { WalletInitializationError } from "@/src/errors/wallet";
import { SecurityEventType } from "@/src/types/security";
import {
Expand Down Expand Up @@ -158,6 +159,9 @@ interface SeizeConnectContextType {

/** Whether another account can be added */
canAddConnectedAccount: boolean;

/** Unread notification counts keyed by connected account address (lowercased) */
connectedAccountUnreadNotifications: Readonly<Record<string, number>>;
}

const SeizeConnectContext = createContext<SeizeConnectContextType | undefined>(
Expand Down Expand Up @@ -1075,6 +1079,9 @@ export const SeizeConnectProvider: React.FC<{ children: React.ReactNode }> = ({
});
}, [activeAddress, liveConnectedAddress, storedConnectedAccounts]);

const connectedAccountUnreadNotifications =
useConnectedAccountsUnreadNotifications(storedConnectedAccounts);

const contextValue = useMemo(
(): SeizeConnectContextType => ({
address: activeAddress,
Expand All @@ -1097,6 +1104,7 @@ export const SeizeConnectProvider: React.FC<{ children: React.ReactNode }> = ({
initializationError,
connectedAccounts,
canAddConnectedAccount,
connectedAccountUnreadNotifications,
}),
[
activeAddress,
Expand All @@ -1117,6 +1125,7 @@ export const SeizeConnectProvider: React.FC<{ children: React.ReactNode }> = ({
hasInitializationError,
initializationError,
canAddConnectedAccount,
connectedAccountUnreadNotifications,
]
);

Expand Down
45 changes: 30 additions & 15 deletions components/header/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export default function AppHeader() {
isAuthenticated,
isConnected,
connectedAccounts,
connectedAccountUnreadNotifications,
seizeSwitchConnectedAccount,
} = useSeizeConnectContext();
const profileClickTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
Expand Down Expand Up @@ -111,6 +112,12 @@ export default function AppHeader() {
isAuthenticated,
isConnected,
});
const hasUnreadOnOtherConnectedProfiles = connectedAccounts.some(
(account) =>
!account.isActive &&
(connectedAccountUnreadNotifications[account.address.toLowerCase()] ?? 0) >
0
);

const pathSegments = pathname.split("/").filter(Boolean);
const basePath = pathSegments.length ? pathSegments[0] : "";
Expand Down Expand Up @@ -173,22 +180,30 @@ export default function AppHeader() {
!!waveId && (isLoading || isFetching || wave?.id !== waveId);

const pfpImage = (
<div
className={`tw-relative tw-h-10 tw-w-10 tw-flex-shrink-0 tw-overflow-hidden tw-rounded-full ${connectionIndicator.avatarClassName}`}
title={connectionIndicator.title}
>
<Image
src={menuAvatarSrc}
alt="pfp"
width={40}
height={40}
className={`tw-h-full tw-w-full tw-bg-iron-900 ${
resolvedPfp ? "tw-object-contain" : "tw-object-cover tw-grayscale"
}`}
/>
{connectionIndicator.overlayClassName && (
<div className="tw-relative tw-h-10 tw-w-10 tw-flex-shrink-0">
<div
className={`tw-relative tw-h-10 tw-w-10 tw-overflow-hidden tw-rounded-full ${connectionIndicator.avatarClassName}`}
title={connectionIndicator.title}
>
<Image
src={menuAvatarSrc}
alt="pfp"
width={40}
height={40}
className={`tw-h-full tw-w-full tw-bg-iron-900 ${
resolvedPfp ? "tw-object-contain" : "tw-object-cover tw-grayscale"
}`}
/>
{connectionIndicator.overlayClassName && (
<div
className={`tw-pointer-events-none tw-absolute tw-inset-0 tw-rounded-full ${connectionIndicator.overlayClassName}`}
/>
)}
</div>
{hasUnreadOnOtherConnectedProfiles && (
<div
className={`tw-pointer-events-none tw-absolute tw-inset-0 tw-rounded-full ${connectionIndicator.overlayClassName}`}
className="tw-absolute tw-right-[-3px] tw-top-[-3px] tw-size-3 tw-rounded-full tw-bg-indigo-500 tw-shadow-sm"
aria-hidden="true"
/>
)}
</div>
Expand Down
52 changes: 36 additions & 16 deletions components/header/AppSidebarConnectedAccounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { useIdentity } from "@/hooks/useIdentity";
function AppSidebarConnectedAccountAvatar({
address,
isConnected,
unreadNotificationsCount,
onSelect,
}: {
readonly address: string;
readonly isConnected: boolean;
readonly unreadNotificationsCount: number;
readonly onSelect: (address: string) => void;
}) {
const { profile, isLoading: isProfileLoading } = useIdentity({
Expand All @@ -30,6 +32,9 @@ function AppSidebarConnectedAccountAvatar({
(shouldShowFallbackPfp ? DEFAULT_CONNECTED_PROFILE_FALLBACK_PFP : null);
const label =
profile?.handle ?? `${address.slice(0, 6)}...${address.slice(-4)}`;
const showUnreadBadge = unreadNotificationsCount > 0;
const unreadBadgeLabel =
unreadNotificationsCount > 99 ? "99+" : unreadNotificationsCount;

return (
<button
Expand All @@ -39,22 +44,31 @@ function AppSidebarConnectedAccountAvatar({
aria-label={`Switch to ${label}`}
title={`${label} • ${connectionIndicator.title}`}
>
<div
className={`tw-relative tw-h-12 tw-w-12 tw-overflow-hidden tw-rounded-full ${connectionIndicator.avatarClassName}`}
>
{avatarSrc && (
<img
src={avatarSrc}
alt={label}
className={`tw-absolute tw-inset-0 tw-block tw-h-full tw-w-full tw-rounded-full tw-bg-iron-900 ${
resolvedPfp ? "tw-object-contain" : "tw-object-cover tw-grayscale"
}`}
/>
)}
{connectionIndicator.overlayClassName && (
<div
className={`tw-pointer-events-none tw-absolute tw-inset-0 tw-rounded-full ${connectionIndicator.overlayClassName}`}
/>
<div className="tw-relative tw-h-12 tw-w-12">
<div
className={`tw-relative tw-h-12 tw-w-12 tw-overflow-hidden tw-rounded-full ${connectionIndicator.avatarClassName}`}
>
{avatarSrc && (
<img
src={avatarSrc}
alt={label}
className={`tw-absolute tw-inset-0 tw-block tw-h-full tw-w-full tw-rounded-full tw-bg-iron-900 ${
resolvedPfp
? "tw-object-contain"
: "tw-object-cover tw-grayscale"
}`}
/>
)}
{connectionIndicator.overlayClassName && (
<div
className={`tw-pointer-events-none tw-absolute tw-inset-0 tw-rounded-full ${connectionIndicator.overlayClassName}`}
/>
)}
</div>
{showUnreadBadge && (
<div className="tw-absolute tw-right-[-4px] tw-top-[-4px] tw-flex tw-h-4 tw-min-w-4 tw-items-center tw-justify-center tw-rounded-full tw-bg-indigo-500 tw-px-1 tw-text-[10px] tw-font-medium tw-text-white tw-shadow-sm">
{unreadBadgeLabel}
</div>
)}
</div>
</button>
Expand All @@ -64,6 +78,7 @@ function AppSidebarConnectedAccountAvatar({
export default function AppSidebarConnectedAccounts() {
const {
connectedAccounts,
connectedAccountUnreadNotifications,
canAddConnectedAccount,
seizeAddConnectedAccount,
seizeSwitchConnectedAccount,
Expand All @@ -84,6 +99,11 @@ export default function AppSidebarConnectedAccounts() {
key={account.address.toLowerCase()}
address={account.address}
isConnected={account.isConnected}
unreadNotificationsCount={
connectedAccountUnreadNotifications[
account.address.toLowerCase()
] ?? 0
}
onSelect={seizeSwitchConnectedAccount}
/>
))}
Expand Down
56 changes: 37 additions & 19 deletions components/header/AppSidebarUserInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import AppSidebarConnectedAccounts from "./AppSidebarConnectedAccounts";
import AppSidebarUserStats from "./AppSidebarUserStats";

export default function AppSidebarUserInfo() {
const { address, isAuthenticated, isConnected } = useSeizeConnectContext();
const {
address,
isAuthenticated,
isConnected,
connectedAccountUnreadNotifications,
} = useSeizeConnectContext();
const { activeProfileProxy } = useAuth();
const { profile } = useIdentity({
handleOrWallet: address ?? null,
Expand Down Expand Up @@ -57,6 +62,12 @@ export default function AppSidebarUserInfo() {
: "User's profile picture";

const source = activeProfileProxy?.created_by ?? profile;
const unreadNotificationsCount = address
? connectedAccountUnreadNotifications[address.toLowerCase()] ?? 0
: 0;
const showUnreadBadge = unreadNotificationsCount > 0;
const unreadBadgeLabel =
unreadNotificationsCount > 99 ? "99+" : unreadNotificationsCount;

const level = source?.level ?? 0;
const tdh = source?.tdh ?? 0;
Expand All @@ -70,25 +81,32 @@ export default function AppSidebarUserInfo() {
<div className="tailwind-scope tw-flex tw-flex-col tw-gap-3 tw-py-2">
<div className="tw-flex tw-w-full tw-items-center tw-gap-2">
{avatarSrc ? (
<div
className={`tw-relative tw-h-12 tw-w-12 tw-overflow-hidden tw-rounded-full ${connectionIndicator.avatarClassName}`}
title={connectionIndicator.title}
>
<Image
src={avatarSrc}
alt={avatarAltText}
fill
sizes="48px"
className={`tw-rounded-full tw-bg-iron-900 ${
resolvedPfp
? "tw-object-contain"
: "tw-object-cover tw-grayscale"
}`}
/>
{connectionIndicator.overlayClassName && (
<div
className={`tw-pointer-events-none tw-absolute tw-inset-0 tw-rounded-full ${connectionIndicator.overlayClassName}`}
<div className="tw-relative tw-h-12 tw-w-12">
<div
className={`tw-relative tw-h-12 tw-w-12 tw-overflow-hidden tw-rounded-full ${connectionIndicator.avatarClassName}`}
title={connectionIndicator.title}
>
<Image
src={avatarSrc}
alt={avatarAltText}
fill
sizes="48px"
className={`tw-rounded-full tw-bg-iron-900 ${
resolvedPfp
? "tw-object-contain"
: "tw-object-cover tw-grayscale"
}`}
/>
{connectionIndicator.overlayClassName && (
<div
className={`tw-pointer-events-none tw-absolute tw-inset-0 tw-rounded-full ${connectionIndicator.overlayClassName}`}
/>
)}
</div>
{showUnreadBadge && (
<div className="tw-absolute tw-right-[-4px] tw-top-[-4px] tw-flex tw-h-4 tw-min-w-4 tw-items-center tw-justify-center tw-rounded-full tw-bg-indigo-500 tw-px-1 tw-text-[10px] tw-font-medium tw-text-white tw-shadow-sm">
{unreadBadgeLabel}
</div>
)}
</div>
) : (
Expand Down
50 changes: 33 additions & 17 deletions components/header/user/connected/HeaderUserConnectedAccounts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ interface ConnectedAccountItem {
readonly address: string;
readonly isActive: boolean;
readonly isConnected: boolean;
readonly unreadNotificationsCount?: number | undefined;
}

const MAX_BADGE_COUNT = 99;

function ConnectedAccountRow({
account,
onSelect,
Expand All @@ -38,6 +41,10 @@ function ConnectedAccountRow({
profile?.handle ??
`${account.address.slice(0, 6)}...${account.address.slice(-4)}`;
const walletLabel = formatAddress(account.address);
const unreadCount = account.unreadNotificationsCount ?? 0;
const showUnreadBadge = unreadCount > 0;
const unreadBadgeLabel =
unreadCount > MAX_BADGE_COUNT ? `${MAX_BADGE_COUNT}+` : unreadCount;

return (
<button
Expand All @@ -50,23 +57,32 @@ function ConnectedAccountRow({
}`}
aria-label={`Switch to ${label} (${walletLabel})`}
>
<div
className={`tw-relative tw-size-8 tw-flex-none tw-overflow-hidden tw-rounded-lg ${connectionIndicator.avatarClassName}`}
title={connectionIndicator.title}
>
{avatarSrc && (
<img
src={avatarSrc}
alt={label}
className={`tw-absolute tw-inset-0 tw-block tw-h-full tw-w-full tw-rounded-lg tw-bg-iron-700 tw-transition tw-duration-300 tw-ease-out ${
resolvedPfp ? "tw-object-contain" : "tw-object-cover tw-grayscale"
}`}
/>
)}
{connectionIndicator.overlayClassName && (
<div
className={`tw-pointer-events-none tw-absolute tw-inset-0 tw-rounded-lg ${connectionIndicator.overlayClassName}`}
/>
<div className="tw-relative tw-size-8 tw-flex-none">
<div
className={`tw-relative tw-size-8 tw-overflow-hidden tw-rounded-lg ${connectionIndicator.avatarClassName}`}
title={connectionIndicator.title}
>
{avatarSrc && (
<img
src={avatarSrc}
alt={label}
className={`tw-absolute tw-inset-0 tw-block tw-h-full tw-w-full tw-rounded-lg tw-bg-iron-700 tw-transition tw-duration-300 tw-ease-out ${
resolvedPfp
? "tw-object-contain"
: "tw-object-cover tw-grayscale"
}`}
/>
)}
{connectionIndicator.overlayClassName && (
<div
className={`tw-pointer-events-none tw-absolute tw-inset-0 tw-rounded-lg ${connectionIndicator.overlayClassName}`}
/>
)}
</div>
{showUnreadBadge && (
<div className="tw-absolute tw-right-[-8px] tw-top-[-8px] tw-flex tw-h-4 tw-min-w-4 tw-items-center tw-justify-center tw-rounded-full tw-bg-indigo-500 tw-px-1 tw-text-[10px] tw-font-medium tw-text-white tw-shadow-sm">
{unreadBadgeLabel}
</div>
)}
</div>

Expand Down
9 changes: 8 additions & 1 deletion components/header/user/proxy/HeaderUserProxyDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default function HeaderUserProxyDropdown({
address,
isConnected,
connectedAccounts,
connectedAccountUnreadNotifications,
canAddConnectedAccount,
seizeConnect,
seizeAddConnectedAccount,
Expand Down Expand Up @@ -132,7 +133,13 @@ export default function HeaderUserProxyDropdown({
{availableConnectedAccounts.length > 0 && (
<div className="tw-mx-0 tw-flex tw-flex-col tw-gap-y-2 tw-px-2">
<HeaderUserConnectedAccounts
accounts={availableConnectedAccounts}
accounts={availableConnectedAccounts.map((account) => ({
...account,
unreadNotificationsCount:
connectedAccountUnreadNotifications[
account.address.toLowerCase()
] ?? 0,
}))}
onSelectAccount={onSelectConnectedAccount}
canAddAccount={canAddConnectedAccount}
onAddAccount={() => {
Expand Down
2 changes: 1 addition & 1 deletion components/layout/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default function AppLayout({ children }: Props) {
<main>{children}</main>
)}
{!isSingleDropOpen && !isStreamRoute && (
<div className="tw-h-16 tw-w-full" />
<div className="tw-h-20 tw-w-full" />
)}
{!isSingleDropOpen && !isEditingOnMobile && (
<BottomNavigation hidden={shouldHideBottomNav} />
Expand Down
Loading