style(rooms): redesign the join-before-navigate screen as a full-width Dawn invite hero with accept and decline
This commit is contained in:
parent
15ce5f4fb9
commit
08456b63ad
2 changed files with 210 additions and 26 deletions
|
|
@ -0,0 +1,53 @@
|
|||
import { style } from '@vanilla-extract/css';
|
||||
import { color, config, toRem } from 'folds';
|
||||
|
||||
// A calm centred invite hero: full-width on mobile, a narrow reading column on
|
||||
// desktop. Replaces the cramped 364px RoomCard that floated in an empty page.
|
||||
export const Hero = style({
|
||||
width: '100%',
|
||||
maxWidth: toRem(440),
|
||||
margin: 'auto',
|
||||
padding: `${config.space.S700} ${config.space.S400}`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
gap: config.space.S400,
|
||||
});
|
||||
|
||||
export const Eyebrow = style({
|
||||
display: 'block',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em',
|
||||
fontSize: toRem(11),
|
||||
fontWeight: config.fontWeight.W600,
|
||||
color: color.Surface.OnContainer,
|
||||
opacity: 0.5,
|
||||
});
|
||||
|
||||
export const Topic = style({
|
||||
color: color.SurfaceVariant.OnContainer,
|
||||
opacity: 0.8,
|
||||
maxWidth: toRem(380),
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 5,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
overflow: 'hidden',
|
||||
whiteSpace: 'pre-wrap',
|
||||
});
|
||||
|
||||
export const MemberCount = style({
|
||||
fontFamily: 'var(--font-mono)',
|
||||
fontVariantNumeric: 'tabular-nums',
|
||||
color: color.SurfaceVariant.OnContainer,
|
||||
opacity: 0.7,
|
||||
});
|
||||
|
||||
// Full-width action stack — comfortable touch targets on native.
|
||||
export const Actions = style({
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: config.space.S200,
|
||||
marginTop: config.space.S300,
|
||||
});
|
||||
|
|
@ -1,15 +1,38 @@
|
|||
import React from 'react';
|
||||
import { Box, Icon, IconButton, Icons, Scroll, Text, toRem } from 'folds';
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
Avatar,
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Icon,
|
||||
IconButton,
|
||||
Icons,
|
||||
Scroll,
|
||||
Spinner,
|
||||
Text,
|
||||
color,
|
||||
toRem,
|
||||
} from 'folds';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { RoomCard } from '../../components/room-card';
|
||||
import { RoomTopicViewer } from '../../components/room-topic-viewer';
|
||||
import { MatrixError, Room } from 'matrix-js-sdk';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import * as css from './JoinBeforeNavigate.css';
|
||||
import { RoomAvatar, RoomIcon } from '../../components/room-avatar';
|
||||
import { Page, PageHeader } from '../../components/page';
|
||||
import { RoomSummaryLoader } from '../../components/RoomSummaryLoader';
|
||||
import { useRoomNavigate } from '../../hooks/useRoomNavigate';
|
||||
import { useMatrixClient } from '../../hooks/useMatrixClient';
|
||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||
import { useJoinedRoomId } from '../../hooks/useJoinedRoomId';
|
||||
import { allRoomsAtom } from '../../state/room-list/roomList';
|
||||
import { ScreenSize, useScreenSizeContext } from '../../hooks/useScreenSize';
|
||||
import { BackRouteHandler } from '../../components/BackRouteHandler';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
|
||||
import { getRoomAvatarUrl } from '../../utils/room';
|
||||
import { RoomType } from '../../../types/matrix/room';
|
||||
import { millify } from '../../plugins/millify';
|
||||
|
||||
type JoinBeforeNavigateProps = { roomIdOrAlias: string; eventId?: string; viaServers?: string[] };
|
||||
export function JoinBeforeNavigate({
|
||||
|
|
@ -17,11 +40,20 @@ export function JoinBeforeNavigate({
|
|||
eventId,
|
||||
viaServers,
|
||||
}: JoinBeforeNavigateProps) {
|
||||
const { t } = useTranslation();
|
||||
const mx = useMatrixClient();
|
||||
const useAuthentication = useMediaAuthentication();
|
||||
const allRooms = useAtomValue(allRoomsAtom);
|
||||
const { navigateRoom, navigateSpace } = useRoomNavigate();
|
||||
const navigate = useNavigate();
|
||||
const screenSize = useScreenSizeContext();
|
||||
|
||||
const joinedRoomId = useJoinedRoomId(allRooms, roomIdOrAlias);
|
||||
// For a pending invite the room already exists locally (membership "invite"),
|
||||
// so resolve it for the richer avatar / member count / membership signal.
|
||||
const room = mx.getRoom(joinedRoomId ?? roomIdOrAlias) ?? undefined;
|
||||
const invited = room?.getMyMembership() === 'invite';
|
||||
|
||||
const handleView = (roomId: string) => {
|
||||
if (mx.getRoom(roomId)?.isSpaceRoom()) {
|
||||
navigateSpace(roomId);
|
||||
|
|
@ -30,6 +62,21 @@ export function JoinBeforeNavigate({
|
|||
navigateRoom(roomId, eventId);
|
||||
};
|
||||
|
||||
const [joinState, join] = useAsyncCallback<Room, MatrixError, []>(
|
||||
useCallback(() => mx.joinRoom(roomIdOrAlias, { viaServers }), [mx, roomIdOrAlias, viaServers])
|
||||
);
|
||||
const joining =
|
||||
joinState.status === AsyncStatus.Loading || joinState.status === AsyncStatus.Success;
|
||||
|
||||
const [declineState, decline] = useAsyncCallback<void, MatrixError, []>(
|
||||
useCallback(async () => {
|
||||
if (room) await mx.leave(room.roomId);
|
||||
navigate('/');
|
||||
}, [mx, room, navigate])
|
||||
);
|
||||
const declining =
|
||||
declineState.status === AsyncStatus.Loading || declineState.status === AsyncStatus.Success;
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader balance>
|
||||
|
|
@ -46,35 +93,119 @@ export function JoinBeforeNavigate({
|
|||
)}
|
||||
</Box>
|
||||
<Box grow="Yes" justifyContent="Center" alignItems="Center" gap="200">
|
||||
<Text size="H3" truncate>
|
||||
{roomIdOrAlias}
|
||||
<Text size="H4" truncate>
|
||||
{room?.name ?? t('Explore.join')}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
</PageHeader>
|
||||
<Box grow="Yes">
|
||||
<Scroll hideTrack visibility="Hover" size="0">
|
||||
<Box style={{ height: '100%' }} grow="Yes" alignItems="Center" justifyContent="Center">
|
||||
<RoomSummaryLoader roomIdOrAlias={roomIdOrAlias}>
|
||||
{(summary) => (
|
||||
<RoomCard
|
||||
style={{ maxWidth: toRem(364), width: '100%' }}
|
||||
roomIdOrAlias={roomIdOrAlias}
|
||||
allRooms={allRooms}
|
||||
avatarUrl={summary?.avatar_url}
|
||||
name={summary?.name}
|
||||
topic={summary?.topic}
|
||||
memberCount={summary?.num_joined_members}
|
||||
roomType={summary?.room_type}
|
||||
viaServers={viaServers}
|
||||
renderTopicViewer={(name, topic, requestClose) => (
|
||||
<RoomTopicViewer name={name} topic={topic} requestClose={requestClose} />
|
||||
)}
|
||||
onView={handleView}
|
||||
{(summary) => {
|
||||
const avatarUrl = room
|
||||
? getRoomAvatarUrl(mx, room, 96, useAuthentication)
|
||||
: (summary?.avatar_url &&
|
||||
mxcUrlToHttp(mx, summary.avatar_url, useAuthentication, 96, 96, 'crop')) ||
|
||||
undefined;
|
||||
const name =
|
||||
room?.name ?? summary?.name ?? getMxIdLocalPart(roomIdOrAlias) ?? roomIdOrAlias;
|
||||
const topic = summary?.topic;
|
||||
const memberCount = room?.getJoinedMemberCount() ?? summary?.num_joined_members;
|
||||
const isSpace = room?.isSpaceRoom() || summary?.room_type === RoomType.Space;
|
||||
const errored = joinState.status === AsyncStatus.Error;
|
||||
|
||||
let primaryLabel = t('Explore.join');
|
||||
if (joining) primaryLabel = t('Explore.joining');
|
||||
else if (invited) primaryLabel = t('Direct.invite_accept');
|
||||
|
||||
return (
|
||||
<div className={css.Hero}>
|
||||
<Avatar size="500" radii="500" style={{ width: toRem(84), height: toRem(84) }}>
|
||||
<RoomAvatar
|
||||
roomId={room?.roomId ?? roomIdOrAlias}
|
||||
src={avatarUrl ?? undefined}
|
||||
alt={name}
|
||||
renderFallback={() => (
|
||||
<RoomIcon
|
||||
size="600"
|
||||
roomType={isSpace ? RoomType.Space : undefined}
|
||||
filled
|
||||
/>
|
||||
)}
|
||||
</RoomSummaryLoader>
|
||||
/>
|
||||
</Avatar>
|
||||
|
||||
{invited && <span className={css.Eyebrow}>{t('Room.invite')}</span>}
|
||||
|
||||
<Box direction="Column" gap="200" alignItems="Center">
|
||||
<Text size="H3" align="Center">
|
||||
{name}
|
||||
</Text>
|
||||
{isSpace && (
|
||||
<Badge variant="Secondary" fill="Soft" outlined>
|
||||
<Text size="L400">{t('Explore.space_badge')}</Text>
|
||||
</Badge>
|
||||
)}
|
||||
{typeof memberCount === 'number' && (
|
||||
<Box gap="100" alignItems="Center">
|
||||
<Icon size="50" src={Icons.User} />
|
||||
<Text size="T200" as="span" className={css.MemberCount}>
|
||||
{t('Explore.members_count', {
|
||||
count: memberCount,
|
||||
formattedCount: millify(memberCount),
|
||||
})}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{topic && (
|
||||
<Text size="T300" className={css.Topic} align="Center">
|
||||
{topic}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
<div className={css.Actions}>
|
||||
{typeof joinedRoomId === 'string' ? (
|
||||
<Button onClick={() => handleView(joinedRoomId)} variant="Primary" size="500">
|
||||
<Text size="B500">{t('Explore.view')}</Text>
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
onClick={join}
|
||||
variant="Primary"
|
||||
size="500"
|
||||
disabled={joining}
|
||||
before={joining && <Spinner size="200" variant="Primary" fill="Solid" />}
|
||||
>
|
||||
<Text size="B500">{primaryLabel}</Text>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{invited && typeof joinedRoomId !== 'string' && (
|
||||
<Button
|
||||
onClick={decline}
|
||||
variant="Secondary"
|
||||
fill="Soft"
|
||||
size="500"
|
||||
disabled={declining}
|
||||
before={declining && <Spinner size="200" variant="Secondary" />}
|
||||
>
|
||||
<Text size="B500">{t('Direct.invite_decline')}</Text>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{errored && (
|
||||
<Text size="T200" align="Center" style={{ color: color.Critical.Main }}>
|
||||
{joinState.error.message || t('Explore.join_error_unknown')}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</RoomSummaryLoader>
|
||||
</Scroll>
|
||||
</Box>
|
||||
</Page>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue