Merge branch 'develop' of https://codeberg.org/mkljczk/pl-fe into develop
Some checks failed
Some checks failed
This commit is contained in:
@ -36,6 +36,7 @@ const statusEventSchema = v.object({
|
||||
end_time: v.fallback(v.nullable(datetimeSchema), null),
|
||||
join_mode: v.fallback(v.nullable(v.picklist(['free', 'restricted', 'invite', 'external'])), null),
|
||||
participants_count: v.fallback(v.number(), 0),
|
||||
participation_request_count: v.fallback(v.number(), 0),
|
||||
location: v.fallback(v.nullable(locationSchema), null),
|
||||
join_state: v.fallback(v.nullable(v.picklist(['pending', 'reject', 'accept'])), null),
|
||||
});
|
||||
|
||||
@ -308,13 +308,7 @@
|
||||
"@stylistic/member-delimiter-style": "error",
|
||||
"promise/catch-or-return": "error",
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"tailwindcss/classnames-order": [
|
||||
"error",
|
||||
{
|
||||
"classRegex": "^(base|container|icon|item|list|outer|wrapper)?[c|C]lass(Name)?$",
|
||||
"config": "tailwind.config.ts"
|
||||
}
|
||||
],
|
||||
"tailwindcss/classnames-order": "off",
|
||||
"tailwindcss/migration-from-tailwind-2": "error",
|
||||
"tailwindcss/no-custom-classname": "off",
|
||||
|
||||
|
||||
@ -126,7 +126,7 @@ const DropdownMenuItem = ({ index, item, onClick, autoFocus, onSetTab }: IDropdo
|
||||
onClick={handleClick}
|
||||
onAuxClick={handleAuxClick}
|
||||
onKeyPress={handleItemKeyPress}
|
||||
target={item.target}
|
||||
target={typeof item.target === 'string' ? item.target : '_blank'}
|
||||
title={item.text}
|
||||
className={
|
||||
clsx('mx-2 my-1 flex cursor-pointer items-center rounded-md px-2 py-1.5 text-sm text-gray-700 hover:bg-gray-100 hover:text-gray-800 focus:bg-gray-100 focus:text-gray-800 focus:outline-none black:hover:bg-gray-900 black:focus:bg-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-200 dark:focus:bg-gray-800 dark:focus:text-gray-200', {
|
||||
|
||||
@ -272,7 +272,7 @@ const SidebarNavigation: React.FC<ISidebarNavigation> = React.memo(({ shrink })
|
||||
|
||||
{features.chats && (
|
||||
<SidebarNavigationLink
|
||||
to='/chats/{-$chatId}'
|
||||
to='/chats'
|
||||
icon={require('@phosphor-icons/core/regular/chats-teardrop.svg')}
|
||||
activeIcon={require('@phosphor-icons/core/fill/chats-teardrop-fill.svg')}
|
||||
count={unreadChatsCount}
|
||||
|
||||
@ -675,7 +675,7 @@ const MenuButton: React.FC<IMenuButton> = ({
|
||||
const account = status.account;
|
||||
|
||||
getOrCreateChatByAccountId(account.id)
|
||||
.then((chat) => navigate({ to: '/chats/{-$chatId}', params: { chatId: chat.id } }))
|
||||
.then((chat) => navigate({ to: '/chats/$chatId', params: { chatId: chat.id } }))
|
||||
.catch(() => { });
|
||||
};
|
||||
|
||||
|
||||
@ -120,7 +120,7 @@ const ThumbNavigation: React.FC = React.memo((): JSX.Element => {
|
||||
src={require('@phosphor-icons/core/regular/chats-teardrop.svg')}
|
||||
activeSrc={require('@phosphor-icons/core/fill/chats-teardrop-fill.svg')}
|
||||
text={intl.formatMessage(messages.chats)}
|
||||
to='/chats/{-$chatId}'
|
||||
to='/chats'
|
||||
exact
|
||||
count={unreadChatsCount}
|
||||
countMax={9}
|
||||
|
||||
@ -166,7 +166,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
||||
toast.error(data?.error);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
navigate({ to: '/chats/{-$chatId}', params: { chatId: response.id } });
|
||||
navigate({ to: '/chats/$chatId', params: { chatId: response.id } });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: ['chats', 'search'],
|
||||
});
|
||||
|
||||
@ -59,7 +59,7 @@ const ChatListItem: React.FC<IChatListItemInterface> = ({ chat, onClick }) => {
|
||||
deleteChat.mutate(undefined, {
|
||||
onSuccess() {
|
||||
if (isUsingMainChatPage) {
|
||||
navigate({ to: '/chats/{-$chatId}' });
|
||||
navigate({ to: '/chats' });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import Button from 'pl-fe/components/ui/button';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'chat_pane.blankslate.title', defaultMessage: 'No messages yet' },
|
||||
body: { id: 'chat_pane.blankslate.body', defaultMessage: 'Search for someone to chat with.' },
|
||||
@ -19,30 +15,21 @@ const Blankslate = ({ onSearch }: IBlankslate) => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Stack
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
className='h-full grow'
|
||||
data-testid='chat-pane-blankslate'
|
||||
>
|
||||
<Stack space={4}>
|
||||
<Stack space={1} className='mx-auto max-w-[80%]'>
|
||||
<Text size='lg' weight='bold' align='center'>
|
||||
{intl.formatMessage(messages.title)}
|
||||
</Text>
|
||||
<div className='⁂-chat-widget__blankslate' data-testid='chat-pane-blankslate'>
|
||||
<div className='⁂-chat-widget__blankslate__text'>
|
||||
<p className='⁂-chat-widget__blankslate__text__title'>
|
||||
{intl.formatMessage(messages.title)}
|
||||
</p>
|
||||
|
||||
<Text theme='muted' align='center'>
|
||||
{intl.formatMessage(messages.body)}
|
||||
</Text>
|
||||
</Stack>
|
||||
<p className='⁂-chat-widget__blankslate__text__body'>
|
||||
{intl.formatMessage(messages.body)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className='mx-auto'>
|
||||
<Button theme='primary' onClick={onSearch}>
|
||||
{intl.formatMessage(messages.action)}
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<button onClick={onSearch}>
|
||||
{intl.formatMessage(messages.action)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context';
|
||||
import { useStatContext } from 'pl-fe/contexts/stat-context';
|
||||
import { useChats } from 'pl-fe/queries/chats';
|
||||
@ -37,9 +36,9 @@ const ChatPane = () => {
|
||||
const renderBody = () => {
|
||||
if (Number(chats?.length) > 0 || showShoutbox || isLoading) {
|
||||
return (
|
||||
<Stack space={4} className='h-full grow'>
|
||||
<div className='⁂-chat-widget__list'>
|
||||
<ChatList onClickChat={handleClickChat} />
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
} else if (chats?.length === 0) {
|
||||
return (
|
||||
|
||||
@ -53,7 +53,7 @@ const ChatSearch: React.FC<IChatSearch> = ({ isMainPage = false }) => {
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
if (isMainPage) {
|
||||
navigate({ to: '/chats/{-$chatId}', params: { chatId: response.id } });
|
||||
navigate({ to: '/chats/$chatId', params: { chatId: response.id } });
|
||||
} else {
|
||||
changeScreen(ChatWidgetScreens.CHAT, response.id);
|
||||
}
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import React, { HTMLAttributes } from 'react';
|
||||
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import IconButton from 'pl-fe/components/ui/icon-button';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import { useSettings } from 'pl-fe/stores/settings';
|
||||
|
||||
interface IChatPaneHeader {
|
||||
@ -37,45 +34,40 @@ const ChatPaneHeader = (props: IChatPaneHeader) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<HStack {...rest} alignItems='center' justifyContent='between' className='h-16 rounded-t-xl px-4 py-3'>
|
||||
<div {...rest} className='⁂-chat-widget__header'>
|
||||
<ButtonComp
|
||||
className='flex h-16 grow flex-row items-center space-x-1'
|
||||
className='⁂-chat-widget__header__title'
|
||||
data-testid='title'
|
||||
{...buttonProps}
|
||||
>
|
||||
<Text weight='semibold' tag='div'>
|
||||
{title}
|
||||
</Text>
|
||||
<div>{title}</div>
|
||||
|
||||
{(!demetricator && typeof unreadCount !== 'undefined' && unreadCount > 0) && (
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Text weight='semibold' data-testid='unread-count'>
|
||||
{(!demetricator && unreadCount !== undefined && unreadCount > 0) && (
|
||||
<div className='⁂-chat-widget__header__count'>
|
||||
<p data-testid='unread-count'>
|
||||
({unreadCount})
|
||||
</Text>
|
||||
</p>
|
||||
|
||||
<div className='size-2.5 rounded-full bg-accent-300' />
|
||||
</HStack>
|
||||
<div className='⁂-chat-widget__header__count__dot' />
|
||||
</div>
|
||||
)}
|
||||
</ButtonComp>
|
||||
|
||||
<HStack space={2} alignItems='center'>
|
||||
<div className='⁂-chat-widget__header__actions'>
|
||||
{secondaryAction ? (
|
||||
<IconButton
|
||||
onClick={secondaryAction}
|
||||
src={secondaryActionIcon as string}
|
||||
iconClassName='h-5 w-5 text-gray-600'
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<IconButton
|
||||
onClick={onToggle}
|
||||
src={require('@phosphor-icons/core/regular/caret-up.svg')}
|
||||
iconClassName={clsx('size-5 text-gray-600 transition-transform', {
|
||||
'rotate-180': isOpen,
|
||||
})}
|
||||
className='⁂-chat-widget__header__open-button'
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import Icon from 'pl-fe/components/ui/icon';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context';
|
||||
@ -21,7 +20,7 @@ const ChatSearchHeader = () => {
|
||||
<ChatPaneHeader
|
||||
data-testid='pane-header'
|
||||
title={
|
||||
<HStack alignItems='center' space={2}>
|
||||
<div className='⁂-chat-widget__search-header'>
|
||||
<button
|
||||
onClick={() => {
|
||||
changeScreen(ChatWidgetScreens.INBOX);
|
||||
@ -36,7 +35,7 @@ const ChatSearchHeader = () => {
|
||||
<Text size='sm' weight='bold' truncate>
|
||||
{intl.formatMessage(messages.title)}
|
||||
</Text>
|
||||
</HStack>
|
||||
</div>
|
||||
}
|
||||
isOpen={isOpen}
|
||||
isToggleable={false}
|
||||
|
||||
@ -3,18 +3,15 @@ import clsx from 'clsx';
|
||||
import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
||||
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import { chatRoute, chatsNewRoute, chatsSettingsRoute, shoutboxRoute } from 'pl-fe/features/ui/router';
|
||||
import { chatsEmptyRoute } from 'pl-fe/features/ui/router';
|
||||
import { useChats } from 'pl-fe/queries/chats';
|
||||
|
||||
import ChatPageSidebar from './components/chat-page-sidebar';
|
||||
import ChatsPageSidebar from './components/chats-page-sidebar';
|
||||
|
||||
const ChatPage: React.FC = () => {
|
||||
const chatMatch = useMatch({ from: chatRoute.id, shouldThrow: false });
|
||||
const onChatRoute = !!chatMatch?.params?.chatId;
|
||||
const onNewRoute = !!useMatch({ from: chatsNewRoute.id, shouldThrow: false });
|
||||
const onSettingsRoute = !!useMatch({ from: chatsSettingsRoute.id, shouldThrow: false });
|
||||
const onShoutboxRoute = !!useMatch({ from: shoutboxRoute.id, shouldThrow: false });
|
||||
const ChatsPage: React.FC = () => {
|
||||
const { chatsQuery: { data: chats } } = useChats();
|
||||
|
||||
const isSidebarHidden = onChatRoute || onNewRoute || onSettingsRoute || onShoutboxRoute;
|
||||
const isSidebarHidden = !useMatch({ from: chatsEmptyRoute.id, shouldThrow: false }) || chats?.length === 0;
|
||||
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const [height, setHeight] = useState<string | number>('100%');
|
||||
@ -49,18 +46,18 @@ const ChatPage: React.FC = () => {
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{ height }}
|
||||
className='h-screen overflow-hidden bg-white text-gray-900 shadow-lg black:bg-transparent dark:bg-primary-900 dark:text-gray-100 dark:shadow-none sm:rounded-t-xl'
|
||||
className='h-screen overflow-hidden bg-white text-gray-900 shadow-lg sm:rounded-t-xl dark:bg-primary-900 dark:text-gray-100 dark:shadow-none black:bg-transparent'
|
||||
>
|
||||
<div
|
||||
className='grid h-full grid-cols-9 overflow-hidden black:divide-gray-800 dark:divide-solid dark:divide-primary-800 sm:black:divide-x sm:dark:divide-x-2'
|
||||
className='grid h-full grid-cols-9 overflow-hidden dark:divide-solid sm:dark:divide-x-2 dark:divide-primary-800 sm:black:divide-x black:divide-gray-800'
|
||||
data-testid='chat-page'
|
||||
>
|
||||
<Stack
|
||||
className={clsx('dark:inset col-span-9 overflow-hidden bg-gradient-to-r from-white to-gray-100 black:bg-black dark:bg-gray-900 dark:bg-none sm:col-span-3', {
|
||||
className={clsx('col-span-9 overflow-hidden bg-gradient-to-r from-white to-gray-100 sm:col-span-3 dark:inset dark:bg-gray-900 dark:bg-none black:bg-black', {
|
||||
'hidden sm:block': isSidebarHidden,
|
||||
})}
|
||||
>
|
||||
<ChatPageSidebar />
|
||||
<ChatsPageSidebar />
|
||||
</Stack>
|
||||
|
||||
<Stack
|
||||
@ -75,4 +72,4 @@ const ChatPage: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export { ChatPage as default };
|
||||
export { ChatsPage as default };
|
||||
@ -9,18 +9,14 @@ import IconButton from 'pl-fe/components/ui/icon-button';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import VerificationBadge from 'pl-fe/components/verification-badge';
|
||||
import { useChatContext } from 'pl-fe/contexts/chat-context';
|
||||
import { chatRoute } from 'pl-fe/features/ui/router';
|
||||
import { useFeatures } from 'pl-fe/hooks/use-features';
|
||||
import { useUnblockAccountMutation, useRelationshipQuery } from 'pl-fe/queries/accounts/use-relationship';
|
||||
import { useChat, useChatActions, useChats } from 'pl-fe/queries/chats';
|
||||
import { useChat, useChatActions } from 'pl-fe/queries/chats';
|
||||
import { useModalsActions } from 'pl-fe/stores/modals';
|
||||
|
||||
import Chat from '../../chat';
|
||||
|
||||
import BlankslateEmpty from './blankslate-empty';
|
||||
import BlankslateWithChats from './blankslate-with-chats';
|
||||
|
||||
const messages = defineMessages({
|
||||
blockMessage: { id: 'chat_settings.block.message', defaultMessage: 'Blocking will prevent this profile from direct messaging you and viewing your content. You can unblock later.' },
|
||||
blockHeading: { id: 'chat_settings.block.heading', defaultMessage: 'Block @{acct}' },
|
||||
@ -36,7 +32,7 @@ const messages = defineMessages({
|
||||
leaveChat: { id: 'chat_settings.options.leave_chat', defaultMessage: 'Leave chat' },
|
||||
});
|
||||
|
||||
const ChatPageMain = () => {
|
||||
const ChatsPageChat = () => {
|
||||
const intl = useIntl();
|
||||
const features = useFeatures();
|
||||
const navigate = useNavigate();
|
||||
@ -45,8 +41,6 @@ const ChatPageMain = () => {
|
||||
|
||||
const { openModal } = useModalsActions();
|
||||
const { data: chat } = useChat(chatId);
|
||||
const { currentChatId } = useChatContext();
|
||||
const { chatsQuery: { data: chats, isLoading } } = useChats();
|
||||
|
||||
const { mutate: unblockAccount } = useUnblockAccountMutation(chat?.account.id!);
|
||||
|
||||
@ -80,25 +74,13 @@ const ChatPageMain = () => {
|
||||
onConfirm: () => {
|
||||
deleteChat.mutate(undefined, {
|
||||
onSuccess() {
|
||||
navigate({ to: '/chats/{-$chatId}' });
|
||||
navigate({ to: '/chats' });
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!currentChatId && chats && chats.length > 0) {
|
||||
return <BlankslateWithChats />;
|
||||
}
|
||||
|
||||
if (!currentChatId) {
|
||||
return <BlankslateEmpty />;
|
||||
}
|
||||
|
||||
if (!chat) {
|
||||
return null;
|
||||
}
|
||||
@ -171,4 +153,4 @@ const ChatPageMain = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export { ChatPageMain as default };
|
||||
export { ChatsPageChat as default };
|
||||
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
|
||||
import { useChats } from 'pl-fe/queries/chats';
|
||||
|
||||
import BlankslateEmpty from './blankslate-empty';
|
||||
import BlankslateWithChats from './blankslate-with-chats';
|
||||
|
||||
const ChatsPageEmpty = () => {
|
||||
const { chatsQuery: { data: chats, isLoading } } = useChats();
|
||||
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (chats && chats.length > 0) {
|
||||
return <BlankslateWithChats />;
|
||||
}
|
||||
|
||||
return <BlankslateEmpty />;
|
||||
};
|
||||
|
||||
export { ChatsPageEmpty as default };
|
||||
@ -13,11 +13,8 @@ const messages = defineMessages({
|
||||
title: { id: 'chat.new_message.title', defaultMessage: 'New Message' },
|
||||
});
|
||||
|
||||
interface IChatPageNew {
|
||||
}
|
||||
|
||||
/** New message form to create a chat. */
|
||||
const ChatPageNew: React.FC<IChatPageNew> = () => {
|
||||
const ChatsPageNew: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@ -28,7 +25,7 @@ const ChatPageNew: React.FC<IChatPageNew> = () => {
|
||||
<IconButton
|
||||
src={require('@phosphor-icons/core/regular/arrow-left.svg')}
|
||||
className='mr-2 size-7 sm:mr-0 sm:hidden rtl:rotate-180'
|
||||
onClick={() => navigate({ to: '/chats/{-$chatId}' })}
|
||||
onClick={() => navigate({ to: '/chats' })}
|
||||
/>
|
||||
|
||||
<CardTitle title={intl.formatMessage(messages.title)} />
|
||||
@ -40,4 +37,4 @@ const ChatPageNew: React.FC<IChatPageNew> = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export { ChatPageNew as default };
|
||||
export { ChatsPageNew as default };
|
||||
@ -30,7 +30,7 @@ const messages = defineMessages({
|
||||
submit: { id: 'chat.page_settings.submit', defaultMessage: 'Save' },
|
||||
});
|
||||
|
||||
const ChatPageSettings = () => {
|
||||
const ChatsPageSettings = () => {
|
||||
const { account } = useOwnAccount();
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
@ -58,7 +58,7 @@ const ChatPageSettings = () => {
|
||||
<IconButton
|
||||
src={require('@phosphor-icons/core/regular/arrow-left.svg')}
|
||||
className='mr-2 size-7 sm:mr-0 sm:hidden rtl:rotate-180'
|
||||
onClick={() => navigate({ to: '/chats/{-$chatId}' })}
|
||||
onClick={() => navigate({ to: '/chats' })}
|
||||
/>
|
||||
|
||||
<CardTitle title={intl.formatMessage(messages.title)} />
|
||||
@ -98,4 +98,4 @@ const ChatPageSettings = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export { ChatPageSettings as default };
|
||||
export { ChatsPageSettings as default };
|
||||
@ -12,7 +12,7 @@ import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config';
|
||||
|
||||
import Shoutbox from '../../shoutbox';
|
||||
|
||||
const ChatPageShoutbox = () => {
|
||||
const ChatsPageShoutbox = () => {
|
||||
const navigate = useNavigate();
|
||||
const instance = useInstance();
|
||||
const { logo } = usePlFeConfig();
|
||||
@ -25,7 +25,7 @@ const ChatPageShoutbox = () => {
|
||||
<IconButton
|
||||
src={require('@phosphor-icons/core/regular/arrow-left.svg')}
|
||||
className='mr-2 size-7 sm:mr-0 sm:hidden rtl:rotate-180'
|
||||
onClick={() => navigate({ to: '/chats/{-$chatId}' })}
|
||||
onClick={() => navigate({ to: '/chats' })}
|
||||
/>
|
||||
|
||||
<Avatar src={logo} alt='' size={40} className='flex-none' />
|
||||
@ -48,4 +48,4 @@ const ChatPageShoutbox = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export { ChatPageShoutbox as default };
|
||||
export { ChatsPageShoutbox as default };
|
||||
@ -15,7 +15,7 @@ const messages = defineMessages({
|
||||
title: { id: 'column.chats', defaultMessage: 'Chats' },
|
||||
});
|
||||
|
||||
const ChatPageSidebar = () => {
|
||||
const ChatsPageSidebar = () => {
|
||||
const intl = useIntl();
|
||||
const navigate = useNavigate();
|
||||
|
||||
@ -23,7 +23,7 @@ const ChatPageSidebar = () => {
|
||||
if (chat === 'shoutbox') {
|
||||
navigate({ to: '/chats/shoutbox' });
|
||||
} else {
|
||||
navigate({ to: '/chats/{-$chatId}', params: { chatId: chat.id } });
|
||||
navigate({ to: '/chats/$chatId', params: { chatId: chat.id } });
|
||||
}
|
||||
};
|
||||
|
||||
@ -64,4 +64,4 @@ const ChatPageSidebar = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export { ChatPageSidebar as default };
|
||||
export { ChatsPageSidebar as default };
|
||||
@ -11,10 +11,7 @@ interface IPane {
|
||||
/** Chat pane UI component for desktop. */
|
||||
const Pane: React.FC<IPane> = ({ isOpen = false, children }) => (
|
||||
<div
|
||||
className={clsx('fixed bottom-0 z-[99] flex w-96 flex-col rounded-t-lg bg-white shadow-3xl black:border black:border-b-0 black:border-gray-800 black:bg-black dark:bg-gray-900 ltr:right-5 rtl:left-5', {
|
||||
'h-[550px] max-h-[100vh]': isOpen,
|
||||
'h-16': !isOpen,
|
||||
})}
|
||||
className={clsx('⁂-chat-widget', { '⁂-chat-widget--open': isOpen })}
|
||||
data-testid='pane'
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -8,10 +8,15 @@ import Spinner from 'pl-fe/components/ui/spinner';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import AccountContainer from 'pl-fe/containers/account-container';
|
||||
import { useAcceptEventParticipationRequestMutation, useEventParticipationRequests, useRejectEventParticipationRequestMutation } from 'pl-fe/queries/events/use-event-participation-requests';
|
||||
import toast from 'pl-fe/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
authorize: { id: 'compose_event.participation_requests.authorize', defaultMessage: 'Authorize' },
|
||||
authorizeSuccess: { id: 'compose_event.participation_requests.authorize.success', defaultMessage: 'Event participation request authorized successfully' },
|
||||
authorizeFail: { id: 'compose_event.participation_requests.authorize.fail', defaultMessage: 'Failed to authorize event participation request' },
|
||||
reject: { id: 'compose_event.participation_requests.reject', defaultMessage: 'Reject' },
|
||||
rejectSuccess: { id: 'compose_event.participation_requests.reject.success', defaultMessage: 'Event participation request rejected successfully' },
|
||||
rejectFail: { id: 'compose_event.participation_requests.reject.fail', defaultMessage: 'Failed to reject event participation request' },
|
||||
});
|
||||
|
||||
interface IAccount {
|
||||
@ -36,13 +41,27 @@ const Account: React.FC<IAccount> = ({ eventId, id, participationMessage }) => {
|
||||
theme='secondary'
|
||||
size='sm'
|
||||
text={intl.formatMessage(messages.authorize)}
|
||||
onClick={() => acceptEventParticipationRequest()}
|
||||
onClick={() => acceptEventParticipationRequest(undefined, {
|
||||
onSuccess: () => {
|
||||
toast.success(messages.authorizeSuccess);
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(messages.authorizeFail);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
<Button
|
||||
theme='danger'
|
||||
size='sm'
|
||||
text={intl.formatMessage(messages.reject)}
|
||||
onClick={() => rejectEventParticipationRequest()}
|
||||
onClick={() => rejectEventParticipationRequest(undefined, {
|
||||
onSuccess: () => {
|
||||
toast.success(messages.rejectSuccess);
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(messages.rejectFail);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</HStack>
|
||||
}
|
||||
@ -63,7 +82,7 @@ const ManagePendingParticipants: React.FC<IManagePendingParticipants> = ({ statu
|
||||
scrollKey={`eventPendingParticipants:${statusId}`}
|
||||
emptyMessageText={<FormattedMessage id='empty_column.event_participant_requests' defaultMessage='There are no pending event participation requests.' />}
|
||||
hasMore={hasNextPage}
|
||||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||
>
|
||||
{accounts.map(({ account_id, participation_message }) =>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import clsx from 'clsx';
|
||||
import fuzzysort from 'fuzzysort';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import React, { useDeferredValue, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { addComposeLanguage, changeComposeLanguage, changeComposeModifiedLanguage, deleteComposeLanguage } from 'pl-fe/actions/compose';
|
||||
@ -96,8 +96,8 @@ const getLanguageDropdown = (composeId: string): React.FC<ILanguageDropdown> =>
|
||||
setSearchValue('');
|
||||
};
|
||||
|
||||
const search = () => {
|
||||
if (searchValue === '') {
|
||||
const search = (value: string) => {
|
||||
if (value === '') {
|
||||
return [...languages].sort((a, b) => {
|
||||
// Push current selection to the top of the list
|
||||
|
||||
@ -124,7 +124,7 @@ const getLanguageDropdown = (composeId: string): React.FC<ILanguageDropdown> =>
|
||||
});
|
||||
}
|
||||
|
||||
return fuzzysort.go(searchValue, languages, {
|
||||
return fuzzysort.go(value, languages, {
|
||||
keys: ['0', '1'],
|
||||
limit: 5,
|
||||
threshold: -10000,
|
||||
@ -143,7 +143,9 @@ const getLanguageDropdown = (composeId: string): React.FC<ILanguageDropdown> =>
|
||||
}, [node.current]);
|
||||
|
||||
const isSearching = searchValue !== '';
|
||||
const results = useMemo(search, [searchValue]);
|
||||
|
||||
const deferredSearchValue = useDeferredValue(searchValue);
|
||||
const results = useMemo(() => search(deferredSearchValue), [deferredSearchValue]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -171,7 +171,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
||||
|
||||
const handleChatClick = () => {
|
||||
getOrCreateChatByAccountId(account.id)
|
||||
.then((chat) => navigate({ to: '/chats/{-$chatId}', params: { chatId: chat.id } }))
|
||||
.then((chat) => navigate({ to: '/chats/$chatId', params: { chatId: chat.id } }))
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
@ -443,7 +443,7 @@ const EventHeader: React.FC<IEventHeader> = ({ status }) => {
|
||||
</span>
|
||||
</HStack>
|
||||
|
||||
{event.join_mode !== 'external' || event.participants_count > 0 && (
|
||||
{(event.join_mode !== 'external' || event.participants_count > 0) && (
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Icon src={require('@phosphor-icons/core/regular/users.svg')} />
|
||||
<a href='#' className='hover:underline' onClick={handleParticipantsClick}>
|
||||
|
||||
@ -176,8 +176,8 @@ const UI: React.FC = React.memo(() => {
|
||||
</Suspense>
|
||||
|
||||
{me && features.chats && (
|
||||
<div className='hidden xl:block'>
|
||||
<Suspense fallback={<div className='fixed bottom-0 z-[99] flex h-16 w-96 animate-pulse flex-col rounded-t-lg bg-white shadow-3xl dark:bg-gray-900 ltr:right-5 rtl:left-5' />}>
|
||||
<div className='⁂-chat-widget__container'>
|
||||
<Suspense fallback={<div className='⁂-chat-widget--placeholder' />}>
|
||||
<ChatWidget />
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
@ -39,10 +39,11 @@ import StatusLayout from 'pl-fe/layouts/status-layout';
|
||||
import { instanceInitialState } from 'pl-fe/reducers/instance';
|
||||
import { isStandalone } from 'pl-fe/utils/state';
|
||||
|
||||
import ChatPageMain from '../../chats/components/chat-page/components/chat-page-main';
|
||||
import ChatPageNew from '../../chats/components/chat-page/components/chat-page-new';
|
||||
import ChatPageSettings from '../../chats/components/chat-page/components/chat-page-settings';
|
||||
import ChatPageShoutbox from '../../chats/components/chat-page/components/chat-page-shoutbox';
|
||||
import ChatsPageChat from '../../chats/components/chats-page/components/chats-page-chat';
|
||||
import ChatsPageEmpty from '../../chats/components/chats-page/components/chats-page-empty';
|
||||
import ChatsPageNew from '../../chats/components/chats-page/components/chats-page-new';
|
||||
import ChatsPageSettings from '../../chats/components/chats-page/components/chats-page-settings';
|
||||
import ChatsPageShoutbox from '../../chats/components/chats-page/components/chats-page-shoutbox';
|
||||
import ColumnLoading from '../components/column-loading';
|
||||
import {
|
||||
AboutPage,
|
||||
@ -566,25 +567,31 @@ export const chatsRoute = createRoute({
|
||||
export const chatsNewRoute = createRoute({
|
||||
getParentRoute: () => chatsRoute,
|
||||
path: '/new',
|
||||
component: ChatPageNew,
|
||||
component: ChatsPageNew,
|
||||
});
|
||||
|
||||
export const chatsSettingsRoute = createRoute({
|
||||
getParentRoute: () => chatsRoute,
|
||||
path: '/settings',
|
||||
component: ChatPageSettings,
|
||||
component: ChatsPageSettings,
|
||||
});
|
||||
|
||||
export const shoutboxRoute = createRoute({
|
||||
getParentRoute: () => chatsRoute,
|
||||
path: '/shoutbox',
|
||||
component: ChatPageShoutbox,
|
||||
component: ChatsPageShoutbox,
|
||||
});
|
||||
|
||||
export const chatRoute = createRoute({
|
||||
getParentRoute: () => chatsRoute,
|
||||
path: '/{-$chatId}',
|
||||
component: ChatPageMain,
|
||||
path: '/$chatId',
|
||||
component: ChatsPageChat,
|
||||
});
|
||||
|
||||
export const chatsEmptyRoute = createRoute({
|
||||
getParentRoute: () => chatsRoute,
|
||||
path: '/',
|
||||
component: ChatsPageEmpty,
|
||||
});
|
||||
|
||||
// Follow requests and blocks
|
||||
@ -736,8 +743,8 @@ export const eventInformationRoute = createRoute({
|
||||
});
|
||||
|
||||
export const eventEditRoute = createRoute({
|
||||
getParentRoute: () => layouts.event,
|
||||
path: '/edit',
|
||||
getParentRoute: () => layouts.events,
|
||||
path: '/@{$username}/events/$statusId/edit',
|
||||
component: EditEvent,
|
||||
beforeLoad: requireAuthMiddleware(({ context: { features } }) => {
|
||||
if (!features.events) throw notFound();
|
||||
@ -1311,6 +1318,7 @@ const routeTree = rootRoute.addChildren([
|
||||
chatsSettingsRoute,
|
||||
shoutboxRoute,
|
||||
chatRoute,
|
||||
chatsEmptyRoute,
|
||||
]),
|
||||
]),
|
||||
layouts.default.addChildren([
|
||||
|
||||
@ -548,8 +548,12 @@
|
||||
"compose_event.fields.start_time_label": "Event start date",
|
||||
"compose_event.fields.start_time_placeholder": "Event begins on…",
|
||||
"compose_event.participation_requests.authorize": "Authorize",
|
||||
"compose_event.participation_requests.authorize.fail": "Failed to authorize event participation request",
|
||||
"compose_event.participation_requests.authorize.success": "Event participation request authorized successfully",
|
||||
"compose_event.participation_requests.authorize_success": "User accepted",
|
||||
"compose_event.participation_requests.reject": "Reject",
|
||||
"compose_event.participation_requests.reject.fail": "Failed to reject event participation request",
|
||||
"compose_event.participation_requests.reject.success": "Event participation request rejected successfully",
|
||||
"compose_event.participation_requests.reject_success": "User rejected",
|
||||
"compose_event.reset_location": "Reset location",
|
||||
"compose_event.submit_success": "Your event was created",
|
||||
|
||||
@ -36,7 +36,7 @@ const DislikesModal: React.FC<BaseModalProps & DislikesModalProps> = ({ onClose,
|
||||
itemClassName='pb-3'
|
||||
style={{ height: 'calc(80vh - 88px)' }}
|
||||
hasMore={hasNextPage}
|
||||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||
useWindowScroll={false}
|
||||
>
|
||||
|
||||
@ -34,8 +34,9 @@ const EventParticipantsModal: React.FC<BaseModalProps & EventParticipantsModalPr
|
||||
emptyMessageText={emptyMessage}
|
||||
listClassName='max-w-full'
|
||||
itemClassName='pb-3'
|
||||
style={{ height: 'calc(80vh - 88px)' }}
|
||||
hasMore={hasNextPage}
|
||||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||
useWindowScroll={false}
|
||||
>
|
||||
|
||||
@ -36,7 +36,7 @@ const FavouritesModal: React.FC<BaseModalProps & FavouritesModalProps> = ({ onCl
|
||||
itemClassName='pb-3'
|
||||
style={{ height: 'calc(80vh - 88px)' }}
|
||||
hasMore={hasNextPage}
|
||||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||
useWindowScroll={false}
|
||||
>
|
||||
|
||||
@ -91,7 +91,7 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
|
||||
})}
|
||||
itemClassName='pb-3'
|
||||
style={{ height: reactions.length > 0 ? 'calc(80vh - 159px)' : 'calc(80vh - 88px)' }}
|
||||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
isLoading={isLoading}
|
||||
useWindowScroll={false}
|
||||
>
|
||||
{accounts.map((account) =>
|
||||
|
||||
@ -36,7 +36,7 @@ const ReblogsModal: React.FC<BaseModalProps & ReblogsModalProps> = ({ onClose, s
|
||||
itemClassName='pb-3'
|
||||
style={{ height: 'calc(80vh - 88px)' }}
|
||||
hasMore={hasNextPage}
|
||||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||
useWindowScroll={false}
|
||||
>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import fuzzysort from 'fuzzysort';
|
||||
import { BookmarkFolder } from 'pl-api';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useDeferredValue, useMemo, useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { ListItem } from 'pl-fe/components/list';
|
||||
@ -38,6 +38,7 @@ const SelectBookmarkFolderModal: React.FC<SelectBookmarkFolderModalProps & BaseM
|
||||
|
||||
const [selectedFolder, setSelectedFolder] = useState(status.bookmark_folder);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const deferredSearchTerm = useDeferredValue(searchTerm);
|
||||
|
||||
const handleSearchChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
||||
setSearchTerm(e.target.value);
|
||||
@ -73,10 +74,10 @@ const SelectBookmarkFolderModal: React.FC<SelectBookmarkFolderModalProps & BaseM
|
||||
const filteredFolders = useMemo(() => {
|
||||
if (!bookmarkFolders) return [];
|
||||
|
||||
const filtered = search(bookmarkFolders, searchTerm);
|
||||
const filtered = search(bookmarkFolders, deferredSearchTerm);
|
||||
|
||||
return filtered;
|
||||
}, [bookmarkFolders, searchTerm]);
|
||||
}, [bookmarkFolders, deferredSearchTerm]);
|
||||
|
||||
let items;
|
||||
|
||||
|
||||
@ -81,7 +81,7 @@ const FollowRequestsPage: React.FC = () => {
|
||||
<ScrollableList
|
||||
scrollKey='followRequests'
|
||||
hasMore={hasNextPage}
|
||||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||
emptyMessageText={<FormattedMessage id='empty_column.follow_requests' defaultMessage="You don't have any follow requests yet. When you receive one, it will show up here." />}
|
||||
>
|
||||
|
||||
@ -22,7 +22,7 @@ const OutgoingFollowRequestsPage: React.FC = () => {
|
||||
<ScrollableList
|
||||
scrollKey='outgoingFollowRequests'
|
||||
hasMore={hasNextPage}
|
||||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||
emptyMessageText={<FormattedMessage id='empty_column.outgoing_follow_requests' defaultMessage="You don't have any outgoing follow requests yet. When you try to follow a user, it will show up here." />}
|
||||
itemClassName='p-2.5'
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ChatProvider } from 'pl-fe/contexts/chat-context';
|
||||
import ChatPage from 'pl-fe/features/chats/components/chat-page/chat-page';
|
||||
import ChatsPage from 'pl-fe/features/chats/components/chats-page/chats-page';
|
||||
|
||||
const ChatIndex: React.FC = () => (
|
||||
<ChatProvider>
|
||||
<ChatPage />
|
||||
<ChatsPage />
|
||||
</ChatProvider>
|
||||
);
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ const QuotesPage: React.FC = () => {
|
||||
statusIds={statusIds}
|
||||
scrollKey={`quotes:${statusId}`}
|
||||
hasMore={hasNextPage}
|
||||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={handleLoadMore}
|
||||
emptyMessageText={emptyMessage}
|
||||
/>
|
||||
|
||||
@ -22,7 +22,7 @@ const ScheduledStatusesPage = () => {
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<ScrollableList
|
||||
hasMore={hasNextPage}
|
||||
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||
isLoading={isLoading}
|
||||
onLoadMore={() => fetchNextPage({ cancelRefetch: false })}
|
||||
emptyMessageText={emptyMessage}
|
||||
listClassName='divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800'
|
||||
|
||||
129
packages/pl-fe/src/styles/new/chats.scss
Normal file
129
packages/pl-fe/src/styles/new/chats.scss
Normal file
@ -0,0 +1,129 @@
|
||||
@use 'mixins';
|
||||
@use 'variables';
|
||||
|
||||
.⁂-chat-widget {
|
||||
@apply fixed bottom-0 z-[99] flex w-96 flex-col rounded-t-lg bg-white shadow-3xl black:border black:border-b-0 black:border-gray-800 black:bg-black dark:bg-gray-900 ltr:right-5 rtl:left-5 h-16;
|
||||
|
||||
&--open {
|
||||
height: 550px;
|
||||
max-height: 100vh;
|
||||
|
||||
.⁂-chat-widget__header__open-button svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&--placeholder {
|
||||
@apply fixed bottom-0 z-[99] flex h-16 w-96 animate-pulse flex-col rounded-t-lg bg-white shadow-3xl dark:bg-gray-900 ltr:right-5 rtl:left-5;
|
||||
}
|
||||
|
||||
&__container {
|
||||
display: none;
|
||||
|
||||
@media (min-width: variables.$breakpoint-xl) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 4rem;
|
||||
border-top-left-radius: 0.75rem;
|
||||
border-top-right-radius: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
gap: 0.5rem;
|
||||
overflow: hidden;
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
height: 4rem;
|
||||
flex-grow: 1;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
&__title div:first-child,
|
||||
&__count p {
|
||||
@include mixins.text($weight: semibold);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&__count {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
&__dot {
|
||||
height: 0.625rem;
|
||||
width: 0.625rem;
|
||||
border-radius: 50%;
|
||||
background: rgb(var(--color-accent-300));
|
||||
}
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
svg {
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
color: rgb(var(--color-gray-600));
|
||||
}
|
||||
}
|
||||
|
||||
&__open-button svg {
|
||||
transform: rotate(0deg);
|
||||
transition: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
}
|
||||
|
||||
&__search-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
&__list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&__blankslate {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
gap: 1rem;
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
max-width: 80%;
|
||||
|
||||
&__title {
|
||||
@include mixins.text($size: lg, $weight: bold, $align: center);
|
||||
}
|
||||
|
||||
&__body {
|
||||
@include mixins.text($theme: muted, $align: center);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
@include mixins.button($theme: primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -6,4 +6,5 @@
|
||||
@use 'timelines';
|
||||
@use 'compose';
|
||||
@use 'drive';
|
||||
@use 'chats';
|
||||
@use 'events';
|
||||
|
||||
Reference in New Issue
Block a user