refactor: mobile UX friendly
This commit is contained in:
@@ -26,7 +26,8 @@ const whiteList = [
|
||||
"getInitialized",
|
||||
"createAdmin",
|
||||
"getBotRelatedChannels",
|
||||
"sendMessageByBot"
|
||||
"sendMessageByBot",
|
||||
"replyWithChatGPT"
|
||||
];
|
||||
|
||||
const baseQuery = fetchBaseQuery({
|
||||
@@ -41,7 +42,7 @@ const baseQuery = fetchBaseQuery({
|
||||
});
|
||||
|
||||
let waitingForRenew: null | any = null;
|
||||
const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
|
||||
const baseQueryWithTokenCheck = async (args: any, api: any, extraOptions: any) => {
|
||||
if (waitingForRenew) {
|
||||
await waitingForRenew;
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ export const onMessageSendStarted = async (
|
||||
|
||||
try {
|
||||
const { data: server_mid } = await queryFulfilled;
|
||||
// throw new Error();
|
||||
// console.log("message server mid", server_mid);
|
||||
batch(() => {
|
||||
dispatch(removeContextMessage({ id, mid: ts }));
|
||||
|
||||
@@ -86,7 +86,7 @@ const FileBox: FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(`h-[66px] rounded-md border border-solid border-gray-300 dark:border-gray-500 bg-gray-100 dark:bg-gray-900`, flex ? "w-full" : "w-[370px]", withPreview && "relative overflow-hidden h-[281px]", file_type.startsWith("audio") && "h-[125px]")}
|
||||
className={clsx(`h-[66px] rounded-md border border-solid border-gray-300 dark:border-gray-500 bg-gray-100 dark:bg-gray-900`, flex ? "w-full" : "w-72 md:w-[370px]", withPreview && "relative overflow-hidden h-[281px]", file_type.startsWith("audio") && "h-[125px]")}
|
||||
>
|
||||
<div className="w-full p-2 flex items-center justify-between gap-2">
|
||||
{icon}
|
||||
@@ -94,13 +94,13 @@ const FileBox: FC<Props> = ({
|
||||
<span className="font-semibold text-sm text-gray-800 dark:text-gray-200 truncate">{name}</span>
|
||||
<em className="text-xs text-gray-500 flex gap-4 not-italic">
|
||||
<span className="size">{formatBytes(size)}</span>
|
||||
<span className="time">{fromNowTime(created_at)}</span>
|
||||
<span className="hidden md:block time">{fromNowTime(created_at)}</span>
|
||||
<span>
|
||||
by <strong className="font-bold">{fromUser.name}</strong>
|
||||
</span>
|
||||
</em>
|
||||
</div>
|
||||
<a className="whitespace-nowrap" download={name} href={`${content}&download=true`}>
|
||||
<a className="hidden md:block whitespace-nowrap" download={name} href={`${content}&download=true`}>
|
||||
<IconDownload className="fill-gray-500 dark:fill-gray-300" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -37,17 +37,17 @@ const VideoMessage = ({ url, name, size, download }: Props) => {
|
||||
};
|
||||
const tipClass = 'absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2';
|
||||
return (
|
||||
<div className='w-64 h-32 md:w-96 md:h-52 relative rounded-md border border-solid border-gray-300 overflow-hidden group'>
|
||||
<div className='w-60 h-32 md:w-96 md:h-52 relative rounded-md border border-solid border-gray-300 dark:border-gray-700 overflow-hidden group'>
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-black/40 z-20 group-hover:hidden"></div>
|
||||
<div className="absolute top-0 left-0 w-full flex justify-between z-30 px-3 py-2 overflow-hidden group-hover:bg-black/20">
|
||||
<div className="flex gap-2 ">
|
||||
<IconVideo className="w-9 h-auto" />
|
||||
<IconVideo className="hidden md:block w-9 h-auto" />
|
||||
<div className="flex flex-col gap-1 text-sm text-white">
|
||||
<span title={name} className='font-bold w-[240px] truncate'>{name}</span>
|
||||
<span title={name} className='font-bold w-56 md:w-[240px] truncate'>{name}</span>
|
||||
<span>{_size}</span>
|
||||
</div>
|
||||
</div>
|
||||
<a href={download} className="mt-2"><IconDownload className="fill-white" /></a>
|
||||
<a href={download} className="hidden md:block mt-2"><IconDownload className="fill-white" /></a>
|
||||
</div>
|
||||
{!canPlay ?
|
||||
<div className={tipClass}>
|
||||
|
||||
@@ -155,12 +155,12 @@ const FileMessage: FC<Props> = ({
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<div className={clsx(`file_message bg-slate-50 dark:bg-slate-900 border border-solid border-gray-300 dark:border-gray-500 box-border w-[370px] rounded-md`, sending && "opacity-90")}>
|
||||
<div className={clsx(`bg-slate-50 dark:bg-slate-900 border border-solid border-gray-300 dark:border-gray-500 box-border md:w-[370px] rounded-md`, sending && "opacity-90")}>
|
||||
<div className="px-2 py-3 flex items-center justify-between gap-2">
|
||||
{icon}
|
||||
<div className="flex flex-col gap-1 w-full overflow-hidden">
|
||||
<span className="font-semibold text-sm text-gray-800 dark:text-gray-100 truncate">{name}</span>
|
||||
<span className="whitespace-nowrap text-xs text-gray-500 dark:text-gray-300 flex gap-4">
|
||||
<span className="hidden md:flex whitespace-nowrap text-xs text-gray-500 dark:text-gray-300 gap-4">
|
||||
{sending ? (
|
||||
<Progress value={progress} width={"80%"} />
|
||||
) : (
|
||||
@@ -179,7 +179,7 @@ const FileMessage: FC<Props> = ({
|
||||
{sending ? (
|
||||
<IconClose className="cursor-pointer" onClick={handleCancel} />
|
||||
) : (
|
||||
<a className="whitespace-nowrap" download={name} href={`${content}&download=true`}>
|
||||
<a className="hidden md:block whitespace-nowrap" download={name} href={`${content}&download=true`}>
|
||||
<IconDownload className="fill-gray-500 dark:fill-gray-400" />
|
||||
</a>
|
||||
)}
|
||||
|
||||
@@ -24,7 +24,7 @@ const FavoredMessage: FC<Props> = ({ id = "" }) => {
|
||||
return (
|
||||
<div className="w-full relative flex items-start gap-3 px-2 py-1 my-2 rounded-lg dark:hover:bg-gray-800" key={idx}>
|
||||
{user && (
|
||||
<div className="shrink-0">
|
||||
<div className="shrink-0 w-10 h-10 flex">
|
||||
<Avatar width={40} height={40} className="rounded-full object-cover" src={user.avatar} name={user.name} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -14,7 +14,7 @@ const renderContent = (data: MessagePayload) => {
|
||||
switch (content_type) {
|
||||
case ContentTypes.text:
|
||||
res = (
|
||||
<span className="truncate break-words break-all text-gray-800 dark:text-gray-100">
|
||||
<span className="md:truncate md:break-words md:break-all text-gray-800 dark:text-gray-100">
|
||||
<LinkifyText text={content} url={false} mentionTextOnly={true} mentionPopOver={false} />
|
||||
</span>
|
||||
);
|
||||
@@ -77,7 +77,7 @@ const Reply: FC<ReplyProps> = ({ mid, interactive = true }) => {
|
||||
<div
|
||||
key={mid}
|
||||
data-mid={mid}
|
||||
className={`flex items-start p-2 bg-gray-100 dark:bg-gray-900 rounded-lg gap-2 mb-1 ${interactive ? "cursor-pointer" : "!bg-transparent"}`}
|
||||
className={`flex items-start flex-col md:flex-row p-2 bg-gray-100 dark:bg-gray-900 rounded-lg gap-2 mb-1 ${interactive ? "cursor-pointer" : "!bg-transparent"}`}
|
||||
onClick={interactive ? handleClick : undefined}
|
||||
>
|
||||
<div className="flex items-center gap-1 whitespace-nowrap">
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function URLPreview({ url = "" }) {
|
||||
if (!url || !data || !data.title) return null;
|
||||
const { title, description, ogImage } = data;
|
||||
|
||||
const containerClass = `flex items-center border border-solid border-gray-300 box-border rounded-md w-[380px]`;
|
||||
const containerClass = `flex items-center border border-solid border-gray-300 dark:border-gray-600 box-border rounded-md w-[80%] md:w-[380px]`;
|
||||
const dotsClass = `truncate`;
|
||||
return ogImage ? (
|
||||
<a className={`${containerClass} flex-col !items-start p-3`} href={url} target="_blank" rel="noreferrer">
|
||||
@@ -55,13 +55,13 @@ export default function URLPreview({ url = "" }) {
|
||||
className={`${containerClass} gap-2 px-2 py-3`}
|
||||
href={url} target="_blank" rel="noreferrer">
|
||||
{favicon && (
|
||||
<div className="flex w-12 h-12 rounded">
|
||||
<img onError={handleFavError} className="object-contain" src={favicon} alt="favicon" />
|
||||
<div className="flex rounded">
|
||||
<img onError={handleFavError} className="object-contain w-12 h-12" src={favicon} alt="favicon" />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col">
|
||||
<h3 className="text-sm text-gray-900">{title}</h3>
|
||||
<p className={`text-xs text-gray-500 w-[288px] ${dotsClass}`}>{description}</p>
|
||||
<h3 className="text-sm text-gray-900 dark:text-gray-100">{title}</h3>
|
||||
<p className={`hidden md:block text-xs text-gray-500 dark:text-gray-400 w-[288px] ${dotsClass}`}>{description}</p>
|
||||
<span className={`text-[10px] text-gray-500 w-[288px] ${dotsClass}`}>{url}</span>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
@@ -96,7 +96,7 @@ const Message: FC<IProps> = ({
|
||||
onContextMenu={readOnly ? undefined : handleContextMenuEvent}
|
||||
data-msg-mid={mid}
|
||||
ref={inviewRef}
|
||||
className={clsx(`group w-full relative flex items-start gap-4 px-2 py-1 my-2 rounded-lg dark:hover:bg-gray-800 hover:bg-gray-100`,
|
||||
className={clsx(`group w-[85%] md:w-full relative flex items-start gap-2 md:gap-4 px-1 md:px-2 py-1 my-2 rounded-lg dark:hover:bg-gray-800 hover:bg-gray-100`,
|
||||
readOnly && "hover:bg-transparent",
|
||||
showExpire && "bg-red-200",
|
||||
pinInfo && "bg-cyan-50 dark:bg-cyan-800 pt-7"
|
||||
@@ -111,7 +111,7 @@ const Message: FC<IProps> = ({
|
||||
trigger="click"
|
||||
content={<Profile uid={fromUid || 0} type="card" cid={context == "user" ? 0 : contextId} />}
|
||||
>
|
||||
<div className="cursor-pointer w-10 h-10 flex-shrink-0" data-uid={fromUid} ref={avatarRef}>
|
||||
<div className="cursor-pointer w-10 h-10 shrink-0" data-uid={fromUid} ref={avatarRef}>
|
||||
<Avatar className="w-10 h-10 rounded-full object-cover" width={40} height={40} src={currUser?.avatar} name={currUser?.name} />
|
||||
</div>
|
||||
</Tippy>
|
||||
|
||||
@@ -45,7 +45,7 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
const canRemoveFromServer = !isCard && canRemove;
|
||||
const hasMore = email || canRemoveFromChannel || canRemoveFromServer;
|
||||
const iconClass = `cursor-pointer flex flex-col items-center gap-1 rounded-lg w-32 text-primary-400 bg-gray-50 dark:bg-gray-800 text-sm pt-3.5 pb-3`;
|
||||
const containerClass = clsx(`flex-center flex-col gap-1 z-[99] mt-20 select-none`, isCard ? "p-4 w-[280px] bg-white dark:bg-gray-800 drop-shadow rounded-md" : "w-[432px]");
|
||||
const containerClass = clsx(`flex-center flex-col gap-1 z-[99] mt-20 select-none`, isCard ? "p-4 w-[280px] bg-white dark:bg-gray-800 drop-shadow rounded-md" : "md:w-[432px]");
|
||||
return (
|
||||
<div className={containerClass}>
|
||||
<Avatar
|
||||
@@ -58,7 +58,7 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
<h2 className="text-lg select-text font-bold text-[#1c1c1e] dark:text-white">{name}</h2>
|
||||
<span className="text-sm text-[#98a2b3] dark:text-gray-200 select-text">{email}</span>
|
||||
{/* <p className="intro">{introduction}</p> */}
|
||||
<ul className={clsx("mt-6 flex items-center gap-2", isCard && "pb-0.5")}>
|
||||
<ul className={clsx("mt-6 flex flex-col md:flex-row items-center gap-2", isCard && "pb-0.5")}>
|
||||
<NavLink to={`/chat/dm/${uid}`}>
|
||||
<li className={`${iconClass} icon chat`}>
|
||||
<IconMessage />
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function EmojiPicker({ selectEmoji }: { selectEmoji: (e: string)
|
||||
|
||||
return (
|
||||
<Tooltip placement="top" tip="Emojis" disabled={visible}>
|
||||
<div className="relative w-fit flex items-center">
|
||||
<div className="hidden md:flex relative w-fit items-center">
|
||||
<div ref={ref} className={clsx(`absolute -top-5 -left-5 -translate-y-full`, visible ? 'block' : 'hidden')}>
|
||||
{/* emoji picker */}
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@ export default function Replying({
|
||||
const user = usersData[from_uid];
|
||||
|
||||
return (
|
||||
<div className="reply bg-gray-100 dark:bg-gray-700 z-[999] flex items-start justify-start gap-4 rounded-t-lg w-full px-4 py-3 text-sm">
|
||||
<div className="reply bg-gray-100 dark:bg-gray-700 z-[999] flex flex-col md:flex-row items-start justify-start gap-4 rounded-t-lg w-full px-4 py-3 text-sm">
|
||||
<div className="whitespace-nowrap text-gray-400 ">
|
||||
Replying to <span className="font-bold text-gray-600 dark:text-gray-400">{user?.name}</span>
|
||||
</div>
|
||||
|
||||
@@ -45,8 +45,8 @@ const Toolbar: FC<Props> = ({
|
||||
|
||||
const isMarkdown = mode == 'markdown';
|
||||
return (
|
||||
<div className={`flex flex-col items-center justify-end gap-2.5 md:flex-row`}>
|
||||
<div className="cursor-pointer flex gap-3.5">
|
||||
<div className={`flex flex-col md:flex-row items-center justify-end gap-2.5`}>
|
||||
<div className="hidden md:flex cursor-pointer gap-3.5">
|
||||
<Tooltip placement="top" tip="Markdown">
|
||||
<MarkdownIcon className={isMarkdown ? "fill-[#22ccee]" : "dark:fill-gray-300"} onClick={toggleMode} />
|
||||
</Tooltip>
|
||||
|
||||
@@ -43,8 +43,8 @@ const User: FC<Props> = ({
|
||||
};
|
||||
if (!curr) return null;
|
||||
const online = curr.online || curr.uid == loginUid;
|
||||
const containerClass = clsx(`relative flex items-center justify-start gap-2 rounded-lg select-none `, interactive && "hover:bg-gray-500/10", compact ? "p-0" : "p-2");
|
||||
const nameClass = clsx(`text-sm text-gray-500 max-w-[190px] truncate font-semibold dark:text-white`);
|
||||
const containerClass = clsx(`relative flex items-center justify-start md:gap-2 rounded-lg select-none `, interactive && "hover:bg-gray-500/10", compact ? "p-0" : "p-2");
|
||||
const nameClass = clsx(`hidden md:block text-sm text-gray-500 max-w-[190px] truncate font-semibold dark:text-white`);
|
||||
const statusClass = clsx(`absolute -bottom-0.5 -right-1.5 w-3 h-3 box-content rounded-full border-[2px] border-solid border-white dark:border-gray-300`,
|
||||
online ? "bg-[#22c55e]" : "bg-[#a1a1aa]",
|
||||
compact && "w-3.5 h-3.5");
|
||||
|
||||
@@ -12,7 +12,7 @@ interface Props {
|
||||
|
||||
const StyledModal: FC<Props> = ({ compact = false, title = "", description = "", buttons, children, className }) => {
|
||||
return (
|
||||
<div className={clsx("rounded-lg bg-white dark:bg-gray-900 drop-shadow", compact ? "p-4 min-w-[406px] text-left" : "p-8 min-w-[440px] text-center", className)} >
|
||||
<div className={clsx("rounded-lg bg-white dark:bg-gray-900 drop-shadow", compact ? "p-4 md:min-w-[406px] text-left" : "p-5 md:p-8 md:min-w-[440px] text-center", className)} >
|
||||
{title && <h3 className="text-xl text-gray-600 dark:text-white mb-4 font-semibold">{title}</h3>}
|
||||
{description && <p className="text-sm text-gray-400 dark:text-gray-100 mb-2">{description}</p>}
|
||||
{children}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { TextareaHTMLAttributes } from "react";
|
||||
import { forwardRef, TextareaHTMLAttributes } from "react";
|
||||
|
||||
type Props = TextareaHTMLAttributes<HTMLTextAreaElement>
|
||||
const StyledTextarea = ({ className, ...rest }: Props) => {
|
||||
return <textarea className={`rounded text-sm p-2 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 resize-none w-full shadow-sm border border-gray-200 dark:border-gray-400
|
||||
const StyledTextarea = forwardRef(({ className, ...rest }: Props, ref) => {
|
||||
return <textarea ref={ref} className={`rounded text-sm p-2 bg-white dark:bg-gray-800 text-gray-700 dark:text-gray-300 resize-none w-full shadow-sm border border-gray-200 dark:border-gray-400
|
||||
disabled:bg-gray-100 dark:disabled:bg-gray-800/50
|
||||
disabled:text-gray-400 dark:disabled:text-gray-500
|
||||
disabled:pointer-events-none
|
||||
placeholder:text-gray-400
|
||||
${className}`}
|
||||
{...rest}></textarea>;
|
||||
};
|
||||
});
|
||||
export default StyledTextarea;
|
||||
|
||||
@@ -10,6 +10,8 @@ import IconImage from "../assets/icons/file.image.svg";
|
||||
import { Archive, ArchiveMessage } from "../types/resource";
|
||||
import { MessagePayload } from "../app/slices/message";
|
||||
|
||||
export const isMobile = () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
||||
|
||||
export const getLocalAuthData = () => {
|
||||
return {
|
||||
token: localStorage.getItem(KEY_TOKEN) || "",
|
||||
|
||||
@@ -136,7 +136,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
|
||||
<div className="flex items-center gap-1">
|
||||
<ChannelIcon personal={!is_public} />
|
||||
<span className="text-gray-800 dark:text-white">{name}</span>
|
||||
<span className="ml-2 text-gray-500">{description}</span>
|
||||
<span className="ml-2 text-gray-500 hidden md:block">{description}</span>
|
||||
</div>
|
||||
</header>
|
||||
}
|
||||
@@ -164,7 +164,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<article className="py-4.5 px-4 w-full h-full overflow-x-hidden overflow-y-auto" id={`VOCECHAT_FEED_channel_${cid}`}>
|
||||
<article className="py-4.5 px-1 md:px-4 w-full h-full overflow-x-hidden overflow-y-auto" id={`VOCECHAT_FEED_channel_${cid}`}>
|
||||
{hasMore ? (
|
||||
<LoadMore pullUp={pullUp} pulling={pulling} />
|
||||
) : (
|
||||
|
||||
@@ -40,7 +40,7 @@ const SessionList: FC<Props> = () => {
|
||||
}, [channelIDs, channelMessage, readChannels, readUsers, loginUid, userMessage]);
|
||||
|
||||
return (
|
||||
<ul className="flex flex-col gap-0.5 p-2 overflow-auto h-[calc(100vh_-_56px_-_16px_-_8px)]">
|
||||
<ul className="flex flex-col gap-0.5 p-2 overflow-auto h-[calc(100vh_-_56px_-_38px)]">
|
||||
{sessions.map((s) => {
|
||||
const { key, id, mid = 0 } = s;
|
||||
return <Session key={key} id={id} mid={mid} />;
|
||||
|
||||
@@ -119,7 +119,7 @@ function FavsPage() {
|
||||
};
|
||||
return (
|
||||
<div className="h-screen flex bg-white mt-2 mr-6 mb-2.5 overflow-auto dark:bg-[#384250] rounded-2xl">
|
||||
<div className="min-w-[268px] p-2 shadow-inner-[-1px_0px_0px_rgba(0,_0,_0,_0.1)]">
|
||||
<div className=" md:min-w-[268px] p-2 shadow-inner-[-1px_0px_0px_rgba(0,_0,_0,_0.1)]">
|
||||
<ul className="flex flex-col gap-0.5">
|
||||
{Filters.map(({ icon, title, filter: f }) => {
|
||||
return (
|
||||
@@ -129,7 +129,7 @@ function FavsPage() {
|
||||
onClick={handleFilter.bind(null, f as filter)}
|
||||
>
|
||||
{icon}
|
||||
<span className="font-bold text-sm text-gray-600 dark:text-gray-100">{title}</span>
|
||||
<span className="hidden md:block font-bold text-sm text-gray-600 dark:text-gray-100">{title}</span>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
|
||||
+10
-19
@@ -9,13 +9,14 @@ import usePreload from "../../common/hook/usePreload";
|
||||
import Tooltip from "../../common/component/Tooltip";
|
||||
import Notification from "../../common/component/Notification";
|
||||
import Manifest from "../../common/component/Manifest";
|
||||
|
||||
import ChatIcon from "../../assets/icons/chat.svg";
|
||||
import UserIcon from "../../assets/icons/user.svg";
|
||||
import FavIcon from "../../assets/icons/bookmark.svg";
|
||||
import FolderIcon from "../../assets/icons/folder.svg";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
// const routes = ["/setting", "/setting/channel/:cid"];
|
||||
import { isMobile } from "../../common/utils";
|
||||
|
||||
|
||||
function HomePage() {
|
||||
const { t } = useTranslation();
|
||||
const isHomePath = useMatch(`/`);
|
||||
@@ -42,16 +43,13 @@ function HomePage() {
|
||||
const isSettingPage = pathname.startsWith("/setting");
|
||||
const isChattingPage = isHomePath || pathname.startsWith("/chat");
|
||||
if (isSettingPage) {
|
||||
return (
|
||||
<>
|
||||
<Outlet />
|
||||
</>
|
||||
);
|
||||
return <Outlet />;
|
||||
}
|
||||
// 有点绕
|
||||
const chatNav = isChatHomePath ? "/chat" : chatPath || "/chat";
|
||||
const userNav = userPath || "/users";
|
||||
const linkClass = `flex items-center gap-2.5 px-3 py-2 font-semibold text-sm text-gray-600 rounded-lg hover:bg-gray-800/10`;
|
||||
const mobile = isMobile();
|
||||
return (
|
||||
<>
|
||||
<Manifest />
|
||||
@@ -62,42 +60,35 @@ function HomePage() {
|
||||
{loginUid && <User uid={loginUid} />}
|
||||
<nav className="flex flex-col gap-1 px-3 py-6">
|
||||
<NavLink
|
||||
className={() => {
|
||||
return `${linkClass} ${isChattingPage ? "bg-primary-400 hover:bg-primary-400" : ""}`;
|
||||
}}
|
||||
className={({ isActive }) => `${linkClass} ${(isActive || isChattingPage) ? "bg-primary-400 hover:bg-primary-400" : ""}`}
|
||||
to={chatNav}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
return <Tooltip tip={t("chat")}>
|
||||
return <Tooltip disabled={mobile} tip={t("chat")}>
|
||||
<ChatIcon className={(isActive || isChattingPage) ? "fill-white" : ""} />
|
||||
</Tooltip>;
|
||||
}}
|
||||
|
||||
</NavLink>
|
||||
<NavLink className={({ isActive }) => `${linkClass} ${isActive ? 'bg-primary-400 hover:bg-primary-400' : ""}`} to={userNav}>
|
||||
{({ isActive }) => {
|
||||
return <Tooltip tip={t("members")}>
|
||||
return <Tooltip disabled={mobile} tip={t("members")}>
|
||||
<UserIcon className={isActive ? "fill-white" : ""} />
|
||||
</Tooltip>;
|
||||
}}
|
||||
|
||||
</NavLink>
|
||||
<NavLink className={({ isActive }) => `${linkClass} ${isActive ? 'bg-primary-400 hover:bg-primary-400' : ""}`} to={"/favs"}>
|
||||
{({ isActive }) => {
|
||||
return <Tooltip tip={t("favs")}>
|
||||
return <Tooltip disabled={mobile} tip={t("favs")}>
|
||||
<FavIcon className={isActive ? "fill-white" : ""} />
|
||||
</Tooltip>;
|
||||
}}
|
||||
|
||||
|
||||
</NavLink>
|
||||
<NavLink className={({ isActive }) => `${linkClass} ${isActive ? 'bg-primary-400 hover:bg-primary-400' : ""}`} to={"/files"}>
|
||||
{({ isActive }) => {
|
||||
return <Tooltip tip={t("files")}>
|
||||
return <Tooltip disabled={mobile} tip={t("files")}>
|
||||
<FolderIcon className={isActive ? "fill-white" : ""} />
|
||||
</Tooltip>;
|
||||
}}
|
||||
|
||||
</NavLink>
|
||||
</nav>
|
||||
<Menu />
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { useState } from "react";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import clsx from "clsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Avatar from "../../../common/component/Avatar";
|
||||
import FilterDate, { Dates } from "./Date";
|
||||
import FilterFrom from "./From";
|
||||
@@ -7,11 +9,9 @@ import FilterChannel from "./Channel";
|
||||
import FilterType, { FileTypes } from "./Type";
|
||||
import ArrowDown from "../../../assets/icons/arrow.down.svg";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import clsx from "clsx";
|
||||
|
||||
const getClass = (selected: boolean) => {
|
||||
return clsx(`cursor-pointer flex items-center gap-2 shadow rounded-lg py-2 px-3 text-xs text-gray-900 dark:text-gray-200`, selected ? 'text-white bg-primary-400' : 'border border-solid border-gray-300 dark:border-gray-400 ');
|
||||
return clsx(`cursor-pointer flex items-center gap-1 md:gap-2 shadow rounded-lg p-1 md:py-2 md:px-3 text-xs text-gray-900 dark:text-gray-200`, selected ? 'text-white bg-primary-400' : 'border border-solid border-gray-300 dark:border-gray-400 ');
|
||||
};
|
||||
export default function Filter({ filter, updateFilter }) {
|
||||
const { t } = useTranslation("file");
|
||||
@@ -22,13 +22,13 @@ export default function Filter({ filter, updateFilter }) {
|
||||
type: false
|
||||
});
|
||||
|
||||
const toggleFilterVisible = (obj) => {
|
||||
const toggleFilterVisible = (obj: any) => {
|
||||
setFiltersVisible((prev) => {
|
||||
return { ...prev, ...obj };
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateFilter = (data) => {
|
||||
const handleUpdateFilter = (data: any) => {
|
||||
updateFilter(data);
|
||||
let _key = Object.keys(data)[0];
|
||||
let tmp = {
|
||||
|
||||
@@ -17,7 +17,7 @@ const Search: FC<Props> = ({ value = "", updateSearchValue = null, embed = false
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx(`relative w-full py-1.5 px-4 shadow`, embed && "py-2 shadow-none")}>
|
||||
<div className={clsx(`hidden md:block relative w-full py-1.5 px-4 shadow`, embed && "py-2 shadow-none")}>
|
||||
<IconSearch className="absolute left-6 top-1/2 -translate-y-1/2" />
|
||||
<input value={value} onChange={handleChange} className="bg-black/5 dark:bg-black/20 rounded-full text-sm text-gray-500 py-2.5 pl-9" placeholder={`${t("action.search")}...`} />
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function View({ view = "item" }: Props) {
|
||||
};
|
||||
const isGrid = view == "grid";
|
||||
return (
|
||||
<ul className={`flex border border-solid dark:border-gray-400 shadow rounded-lg box-border`}>
|
||||
<ul className={`hidden md:flex border border-solid dark:border-gray-400 shadow rounded-lg box-border`}>
|
||||
<li className={getClass(!isGrid)} data-view={"item"} onClick={handleChangeView}>
|
||||
<IconList className={`${!isGrid ? "fill-primary-400" : ""} dark:fill-gray-400`} />
|
||||
</li>
|
||||
|
||||
@@ -82,9 +82,8 @@ function ResourceManagement({ fileMessages }) {
|
||||
}, [view, filter]);
|
||||
|
||||
return (
|
||||
<div className="h-screen overflow-y-scroll flex flex-col items-start my-2 mr-6 rounded-2xl bg-white dark:bg-[#384250]">
|
||||
<div className="h-screen md:overflow-y-scroll flex flex-col items-start my-2 mr-6 rounded-2xl bg-white dark:bg-[#384250]">
|
||||
<Search value={filter.name} updateSearchValue={handleUpdateSearch} />
|
||||
<div className="divider"></div>
|
||||
<div className="flex justify-between w-full px-4 py-5">
|
||||
<Filter filter={filter} updateFilter={updateFilter} />
|
||||
<View view={view} />
|
||||
|
||||
@@ -61,12 +61,12 @@ export default function MyAccount() {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col items-start gap-8">
|
||||
<div className="p-6 flex flex-col items-center w-[512px] bg-gray-100 dark:bg-gray-800 rounded-2xl">
|
||||
<div className="p-6 flex flex-col items-center md:w-[512px] bg-gray-100 dark:bg-gray-800 rounded-2xl">
|
||||
<AvatarUploader url={avatar} name={name} uploadImage={uploadAvatar} />
|
||||
<div className="mt-2 mb-16 font-bold text-lg text-gray-800 dark:text-white">
|
||||
{name} <span className="font-normal text-gray-500">#{uid}</span>
|
||||
</div>
|
||||
<div className="w-full flex justify-between mb-6">
|
||||
<div className="w-full flex flex-col items-start md:flex-row justify-between mb-6">
|
||||
<div className="flex flex-col text-gray-500 dark:text-gray-50">
|
||||
<span className="text-xs uppercase font-semibold">{t("username")}</span>
|
||||
<span className="text-sm ">
|
||||
@@ -77,7 +77,7 @@ export default function MyAccount() {
|
||||
{ct("action.edit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="w-full flex justify-between mb-6">
|
||||
<div className="w-full flex flex-col items-start md:flex-row justify-between mb-6">
|
||||
<div className="flex flex-col text-gray-500 dark:text-gray-50">
|
||||
<span className="text-xs uppercase font-semibold">{t("email")}</span>
|
||||
<span className="text-sm">{email}</span>
|
||||
@@ -86,7 +86,7 @@ export default function MyAccount() {
|
||||
{ct("action.edit")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="w-full flex justify-between mb-6">
|
||||
<div className="w-full flex flex-col items-start md:flex-row justify-between mb-6">
|
||||
<div className="flex flex-col text-gray-500 dark:text-gray-50">
|
||||
<span className="text-xs uppercase font-semibold">{t("password")}</span>
|
||||
<span className="text-sm">*********</span>
|
||||
|
||||
@@ -15,7 +15,7 @@ const Search: FC<Props> = ({ input, updateInput }) => {
|
||||
updateInput(evt.target.value);
|
||||
};
|
||||
return (
|
||||
<div className="relative min-h-[56px] px-2 py-3 flex items-center justify-between gap-2 shadow-[rgb(0_0_0_/_10%)_0px_1px_0px] dark:border-b-gray-500">
|
||||
<div className="hidden md:flex relative min-h-[56px] px-2 py-3 items-center justify-between gap-2 shadow-[rgb(0_0_0_/_10%)_0px_1px_0px] dark:border-b-gray-500">
|
||||
<div className="flex items-center gap-1">
|
||||
<img src={searchIcon} alt="search icon" />
|
||||
<input value={input} placeholder={`${t("action.search")}...`} className="w-full text-sm bg-transparent dark:text-gray-50" onChange={handleInput} />
|
||||
|
||||
@@ -25,11 +25,11 @@ function UsersPage() {
|
||||
if (!users) return null;
|
||||
|
||||
return (
|
||||
<div className="flex h-full pt-2 pr-12 pb-2.5">
|
||||
<div className="rounded-l-2xl bg-white dark:bg-[#1F2A37] relative flex flex-col min-w-[268px] shadow-[inset_-1px_0px_0px_rgba(0,_0,_0,_0.1)]">
|
||||
<div className="flex h-full pt-2 md:pr-12 pb-2.5">
|
||||
<div className="rounded-l-2xl bg-white dark:bg-[#1F2A37] relative flex flex-col md:min-w-[268px] shadow-[inset_-1px_0px_0px_rgba(0,_0,_0,_0.1)]">
|
||||
<Search input={input} updateInput={updateInput} />
|
||||
<div className="px-2 py-3 overflow-scroll pb-[50px]">
|
||||
<nav className="flex flex-col gap-1">
|
||||
<div className="px-2 py-3 overflow-scroll">
|
||||
<nav className="flex flex-col md:gap-1">
|
||||
{users.map(({ uid }) => {
|
||||
return (
|
||||
<NavLink key={uid} className={({ isActive }) => `rounded-md hover:bg-gray-500/10 ${isActive ? "bg-gray-500/10" : ""}`} to={`/users/${uid}`}>
|
||||
|
||||
Reference in New Issue
Block a user