mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
Revamp user profile with tabbed layout (Badge, Amici, Stanze, Gruppi)
- Replace grid layout with NitroCard.Tabs for Badge, Amici, Stanze, Gruppi - Add BadgeInfoView component with hover tooltip showing badge name/description - Add Stanze tab that fetches and lists user rooms via NavigatorSearchComposer - Bold username in profile header - Badge tab with styled grid slots and empty state - Amici tab with loading state - Gruppi tab with full GroupsContainerView
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
import { FC, useState } from 'react';
|
||||
import { LocalizeBadgeDescription, LocalizeBadgeName } from '../../api';
|
||||
import { Flex, LayoutBadgeImageView } from '../../common';
|
||||
|
||||
interface BadgeInfoViewProps
|
||||
{
|
||||
badgeCode: string;
|
||||
}
|
||||
|
||||
export const BadgeInfoView: FC<BadgeInfoViewProps> = props =>
|
||||
{
|
||||
const { badgeCode } = props;
|
||||
const [ isHovered, setIsHovered ] = useState(false);
|
||||
|
||||
return (
|
||||
<Flex center
|
||||
className="w-[45px] h-[45px] rounded bg-white/50 relative cursor-pointer"
|
||||
onMouseEnter={ () => setIsHovered(true) }
|
||||
onMouseLeave={ () => setIsHovered(false) }
|
||||
>
|
||||
<LayoutBadgeImageView badgeCode={ badgeCode } />
|
||||
{ isHovered && (
|
||||
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-1 z-50 bg-white text-black rounded shadow-lg py-1 px-2 text-xs w-[180px] pointer-events-none">
|
||||
<div className="absolute -top-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-white rotate-45" />
|
||||
<div className="font-bold mb-0.5">{ LocalizeBadgeName(badgeCode) }</div>
|
||||
<div className="text-gray-600">{ LocalizeBadgeDescription(badgeCode) }</div>
|
||||
</div>
|
||||
) }
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
@@ -37,7 +37,7 @@ export const UserContainerView: FC<{
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col gap-0">
|
||||
<p className="leading-tight">{ userProfile.username }</p>
|
||||
<p className="leading-tight font-bold">{ userProfile.username }</p>
|
||||
<p className="text-sm italic leading-tight">{ userProfile.motto }</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-1">
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
import { CreateLinkEvent, ExtendedProfileChangedMessageEvent, GetSessionDataManager, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomEngineObjectEvent, RoomObjectCategory, RoomObjectType, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
|
||||
import { ExtendedProfileChangedMessageEvent, GetSessionDataManager, NavigatorSearchComposer, NavigatorSearchEvent, RelationshipStatusInfoEvent, RelationshipStatusInfoMessageParser, RoomDataParser, RoomEngineObjectEvent, RoomObjectCategory, RoomObjectType, UserCurrentBadgesComposer, UserCurrentBadgesEvent, UserProfileEvent, UserProfileParser, UserRelationshipsComposer } from '@nitrots/nitro-renderer';
|
||||
import { FC, useState } from 'react';
|
||||
import { GetRoomSession, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api';
|
||||
import { Flex, Grid, LayoutBadgeImageView, Text } from '../../common';
|
||||
import { CreateRoomSession, GetRoomSession, GetUserProfile, LocalizeText, SendMessageComposer } from '../../api';
|
||||
import { Flex, Text } from '../../common';
|
||||
import { BadgeInfoView } from './BadgeInfoView';
|
||||
import { useMessageEvent, useNitroEvent } from '../../hooks';
|
||||
import { NitroCard } from '../../layout';
|
||||
import { FriendsContainerView } from './FriendsContainerView';
|
||||
import { GroupsContainerView } from './GroupsContainerView';
|
||||
import { UserContainerView } from './UserContainerView';
|
||||
|
||||
type ProfileTab = 'badge' | 'amici' | 'stanze' | 'gruppi';
|
||||
|
||||
export const UserProfileView: FC<{}> = props =>
|
||||
{
|
||||
const [ userProfile, setUserProfile ] = useState<UserProfileParser>(null);
|
||||
const [ userBadges, setUserBadges ] = useState<string[]>([]);
|
||||
const [ userRelationships, setUserRelationships ] = useState<RelationshipStatusInfoMessageParser>(null);
|
||||
const [ activeTab, setActiveTab ] = useState<ProfileTab>('badge');
|
||||
const [ userRooms, setUserRooms ] = useState<RoomDataParser[]>(null);
|
||||
|
||||
const onClose = () =>
|
||||
{
|
||||
setUserProfile(null);
|
||||
setUserBadges([]);
|
||||
setUserRelationships(null);
|
||||
setActiveTab('badge');
|
||||
setUserRooms(null);
|
||||
};
|
||||
|
||||
const onLeaveGroup = () =>
|
||||
@@ -28,6 +35,16 @@ export const UserProfileView: FC<{}> = props =>
|
||||
GetUserProfile(userProfile.id);
|
||||
};
|
||||
|
||||
const onTabClick = (tab: ProfileTab) =>
|
||||
{
|
||||
setActiveTab(tab);
|
||||
|
||||
if(tab === 'stanze' && !userRooms && userProfile)
|
||||
{
|
||||
SendMessageComposer(new NavigatorSearchComposer('hotel_view', `owner:${ userProfile.username }`));
|
||||
}
|
||||
};
|
||||
|
||||
useMessageEvent<UserCurrentBadgesEvent>(UserCurrentBadgesEvent, event =>
|
||||
{
|
||||
const parser = event.getParser();
|
||||
@@ -63,6 +80,8 @@ export const UserProfileView: FC<{}> = props =>
|
||||
{
|
||||
setUserBadges([]);
|
||||
setUserRelationships(null);
|
||||
setActiveTab('badge');
|
||||
setUserRooms(null);
|
||||
}
|
||||
|
||||
SendMessageComposer(new UserCurrentBadgesComposer(parser.id));
|
||||
@@ -78,6 +97,28 @@ export const UserProfileView: FC<{}> = props =>
|
||||
GetUserProfile(parser.userId);
|
||||
});
|
||||
|
||||
useMessageEvent<NavigatorSearchEvent>(NavigatorSearchEvent, event =>
|
||||
{
|
||||
if(!userProfile || activeTab !== 'stanze') return;
|
||||
|
||||
const parser = event.getParser();
|
||||
const result = parser.result;
|
||||
|
||||
if(!result) return;
|
||||
|
||||
const rooms: RoomDataParser[] = [];
|
||||
|
||||
for(const resultList of result.results)
|
||||
{
|
||||
if(resultList.rooms && resultList.rooms.length)
|
||||
{
|
||||
for(const room of resultList.rooms) rooms.push(room);
|
||||
}
|
||||
}
|
||||
|
||||
setUserRooms(rooms);
|
||||
});
|
||||
|
||||
useNitroEvent<RoomEngineObjectEvent>(RoomEngineObjectEvent.SELECTED, event =>
|
||||
{
|
||||
if(!userProfile) return;
|
||||
@@ -98,27 +139,79 @@ export const UserProfileView: FC<{}> = props =>
|
||||
<NitroCard.Header
|
||||
headerText={ LocalizeText('extendedprofile.caption') }
|
||||
onCloseClick={ onClose } />
|
||||
<NitroCard.Content
|
||||
className="overflow-hidden">
|
||||
<Grid fullHeight={ false } gap={ 2 }>
|
||||
<div className="flex flex-col col-span-7 gap-1 border-r border-r-gray pe-2">
|
||||
<UserContainerView userProfile={ userProfile } />
|
||||
<div className="flex items-center justify-center w-full gap-3 p-2 rounded bg-muted">
|
||||
{ userBadges && (userBadges.length > 0) && userBadges.map((badge, index) => <LayoutBadgeImageView key={ badge } badgeCode={ badge } />) }
|
||||
<NitroCard.Content className="overflow-hidden !p-0 flex flex-col">
|
||||
<div className="p-2">
|
||||
<UserContainerView userProfile={ userProfile } />
|
||||
</div>
|
||||
<NitroCard.Tabs>
|
||||
<NitroCard.TabItem isActive={ activeTab === 'badge' } count={ userBadges.length } onClick={ () => onTabClick('badge') }>
|
||||
Badge
|
||||
</NitroCard.TabItem>
|
||||
<NitroCard.TabItem isActive={ activeTab === 'amici' } count={ userProfile.friendsCount } onClick={ () => onTabClick('amici') }>
|
||||
Amici
|
||||
</NitroCard.TabItem>
|
||||
<NitroCard.TabItem isActive={ activeTab === 'stanze' } onClick={ () => onTabClick('stanze') }>
|
||||
Stanze
|
||||
</NitroCard.TabItem>
|
||||
<NitroCard.TabItem isActive={ activeTab === 'gruppi' } count={ userProfile.groups?.length } onClick={ () => onTabClick('gruppi') }>
|
||||
Gruppi
|
||||
</NitroCard.TabItem>
|
||||
</NitroCard.Tabs>
|
||||
<div className="flex-1 overflow-auto p-2">
|
||||
{ activeTab === 'badge' && (
|
||||
<div className="flex flex-wrap content-start gap-2 p-2 rounded bg-muted h-full">
|
||||
{ userBadges && (userBadges.length > 0)
|
||||
? userBadges.map((badge, index) => (
|
||||
<BadgeInfoView key={ badge + index } badgeCode={ badge } />
|
||||
))
|
||||
: (
|
||||
<Flex center fullWidth className="h-full">
|
||||
<Text small variant="muted">Nessun badge da mostrare</Text>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col col-span-5">
|
||||
{ userRelationships &&
|
||||
<FriendsContainerView friendsCount={ userProfile.friendsCount } relationships={ userRelationships } /> }
|
||||
</div>
|
||||
</Grid>
|
||||
<Flex alignItems="center" className="px-2 py-1 border-t border-b border-t-gray border-b-gray">
|
||||
<Flex alignItems="center" gap={ 1 } onClick={ event => CreateLinkEvent(`navigator/search/hotel_view/owner:${ userProfile.username }`) }>
|
||||
<i className="nitro-icon icon-rooms" />
|
||||
<Text bold pointer underline>{ LocalizeText('extendedprofile.rooms') }</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<GroupsContainerView fullWidth groups={ userProfile.groups } itsMe={ userProfile.id === GetSessionDataManager().userId } onLeaveGroup={ onLeaveGroup } />
|
||||
) }
|
||||
{ activeTab === 'amici' && (
|
||||
<div className="flex flex-col gap-2 h-full">
|
||||
{ userRelationships ? (
|
||||
<FriendsContainerView friendsCount={ userProfile.friendsCount } relationships={ userRelationships } />
|
||||
) : (
|
||||
<Flex center className="h-full">
|
||||
<Text small variant="muted">Caricamento...</Text>
|
||||
</Flex>
|
||||
) }
|
||||
</div>
|
||||
) }
|
||||
{ activeTab === 'stanze' && (
|
||||
<div className="flex flex-col gap-1 h-full">
|
||||
{ !userRooms && (
|
||||
<Flex center className="h-full">
|
||||
<Text small variant="muted">Caricamento stanze...</Text>
|
||||
</Flex>
|
||||
) }
|
||||
{ userRooms && userRooms.length === 0 && (
|
||||
<Flex center className="h-full">
|
||||
<Text small variant="muted">Nessuna stanza trovata</Text>
|
||||
</Flex>
|
||||
) }
|
||||
{ userRooms && userRooms.length > 0 && userRooms.map(room => (
|
||||
<Flex key={ room.roomId } alignItems="center" gap={ 2 } className="px-2 py-1.5 rounded bg-white/50 cursor-pointer hover:bg-white/80" onClick={ () => CreateRoomSession(room.roomId) }>
|
||||
<div className="flex flex-col min-w-0 grow">
|
||||
<Text bold small truncate>{ room.roomName }</Text>
|
||||
{ room.description && <Text small truncate variant="muted">{ room.description }</Text> }
|
||||
</div>
|
||||
<Text small variant="muted" className="shrink-0">{ room.userCount }/{ room.maxUserCount }</Text>
|
||||
</Flex>
|
||||
)) }
|
||||
</div>
|
||||
) }
|
||||
{ activeTab === 'gruppi' && (
|
||||
<div className="h-full">
|
||||
<GroupsContainerView fullWidth groups={ userProfile.groups } itsMe={ userProfile.id === GetSessionDataManager().userId } onLeaveGroup={ onLeaveGroup } />
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
</NitroCard.Content>
|
||||
</NitroCard>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user