refactor: new mobile layout
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 4L6 8L10 12" stroke="black" stroke-opacity="0.3" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" stroke="black" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 4L6 8L10 12" stroke-opacity="0.8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 215 B |
@@ -38,10 +38,10 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
const chatHandler = type == "chat" ? toggleChannelModalVisible : toggleUserListVisible;
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-8 -mt-[50px] dark:bg-[#384250]">
|
||||
<div className="flex flex-col gap-8 -mt-[50px] dark:bg-gray-700">
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<h2 className="text-center text-3xl text-[#344054] dark:text-white font-bold">{t("title", { name: server.name })}</h2>
|
||||
<p className="text-sm text-[#98a2b3] max-w-[424px] text-center">
|
||||
<p className="text-sm text-gray-400 max-w-[424px] text-center">
|
||||
{t("desc")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -4,8 +4,8 @@ interface Props {
|
||||
}
|
||||
|
||||
const Divider: FC<Props> = ({ content }) => {
|
||||
return <div className="relative border-none h-[1px] bg-[#e3e5e8] dark:bg-gray-500 my-6 overflow-visible">
|
||||
<span className="p-1 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-xs text-gray-500 dark:text-gray-300 font-semibold bg-white dark:bg-[#384250]">{content}</span>
|
||||
return <div className="relative border-none h-[1px] bg-slate-200 dark:bg-gray-500 my-6 overflow-visible">
|
||||
<span className="p-1 absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-xs text-gray-500 dark:text-gray-300 font-semibold bg-white dark:bg-gray-700">{content}</span>
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ interface Props {
|
||||
const Progress: FC<Props> = ({ value, width = "100%" }) => {
|
||||
return (
|
||||
<div className="bg-[#ecfdff] rounded h-2 overflow-hidden" style={{ width }}>
|
||||
<div className="h-2 bg-[#088ab2] rounded transition-all" style={{ width: `${value}%` }}></div>
|
||||
<div className="h-2 bg-primary-700 rounded transition-all" style={{ width: `${value}%` }}></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ const GoogleLoginInner: FC<Props> = ({ type = "login", loaded, loadError }) => {
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<Button className=" group relative w-full !bg-white dark:!bg-[#384250] !text-gray-600 dark:!text-gray-200 !h-[46px] overflow-hidden border border-solid border-[#d0d5dd]" disabled={!loaded || isLoading}>
|
||||
<Button className=" group relative w-full !bg-white dark:!bg-gray-700 !text-gray-600 dark:!text-gray-200 !h-[46px] overflow-hidden border border-solid border-[#d0d5dd]" disabled={!loaded || isLoading}>
|
||||
<div className="absolute left-0 top-0 w-full flex-center gap-3 z-[998] h-10 bg-inherit">
|
||||
<IconGoogle className="w-6 h-6" />
|
||||
{loadError
|
||||
|
||||
@@ -105,27 +105,27 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
|
||||
trigger="click"
|
||||
content={<ReactionPicker mid={mid} hidePicker={hideAll} />}
|
||||
>
|
||||
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800">
|
||||
<li className="flex cursor-pointer p-1 hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
<Tooltip placement="top" tip={t("action.add_reaction")}>
|
||||
<img src={reactIcon} className="toggler w-6 h-6" alt="icon emoji" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
</Tippy>
|
||||
{canEdit && (
|
||||
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800" onClick={toggleEditMessage}>
|
||||
<li className="flex cursor-pointer p-1 hover:bg-gray-100 dark:hover:bg-gray-800" onClick={toggleEditMessage}>
|
||||
<Tooltip placement="top" tip={t("action.edit")}>
|
||||
<img src={editIcon} className="w-6 h-6" alt="icon edit" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
)}
|
||||
{canReply && (
|
||||
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800" onClick={handleReply}>
|
||||
<li className="flex cursor-pointer p-1 hover:bg-gray-100 dark:hover:bg-gray-800" onClick={handleReply}>
|
||||
<Tooltip placement="top" tip={t("action.reply")}>
|
||||
<img src={replyIcon} className="w-6 h-6" alt="icon reply" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
)}
|
||||
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800" onClick={handleAddFav}>
|
||||
<li className="flex cursor-pointer p-1 hover:bg-gray-100 dark:hover:bg-gray-800" onClick={handleAddFav}>
|
||||
<Tooltip placement="top" tip={t("action.add_to_fav")}>
|
||||
<IconBookmark className="fill-[#667085] w-6 h-6" />
|
||||
</Tooltip>
|
||||
@@ -166,7 +166,7 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
|
||||
/>
|
||||
}
|
||||
>
|
||||
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800">
|
||||
<li className="flex cursor-pointer p-1 hover:bg-gray-100 dark:hover:bg-gray-800">
|
||||
<Tooltip placement="top" tip={t("more")}>
|
||||
<img src={moreIcon} alt="icon more" />
|
||||
</Tooltip>
|
||||
|
||||
@@ -56,7 +56,7 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
name={name}
|
||||
/>
|
||||
<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>
|
||||
<span className="text-sm text-gray-400 dark:text-gray-200 select-text">{email}</span>
|
||||
{/* <p className="intro">{introduction}</p> */}
|
||||
<ul className={clsx("mt-6 flex flex-col md:flex-row items-center gap-2", isCard && "pb-0.5")}>
|
||||
<NavLink to={`/chat/dm/${uid}`}>
|
||||
|
||||
@@ -145,7 +145,7 @@ const Send: FC<IProps> = ({
|
||||
{replying_mid && <Replying context={context} mid={replying_mid} id={id} />}
|
||||
{mode == Modes.text && <UploadFileList context={context} id={id} />}
|
||||
|
||||
<div className={clsx(`flex justify-between items-center gap-4 px-4 py-3.5`, isMarkdownMode && `grid grid-cols-[1fr_1fr] grid-rows-[auto_auto] gap-0`)}>
|
||||
<div className={clsx(`flex justify-between items-center gap-4 px-2 md:px-4 py-1 md:py-3.5`, isMarkdownMode && `grid grid-cols-[1fr_1fr] grid-rows-[auto_auto] gap-0`)}>
|
||||
<EmojiPicker selectEmoji={insertEmoji} />
|
||||
{mode == Modes.text && (
|
||||
<MixedInput
|
||||
|
||||
@@ -27,7 +27,7 @@ const StyledSettingContainer: FC<PropsWithChildren<Props>> = ({
|
||||
const { pathname } = useLocation();
|
||||
return (
|
||||
<div className="w-screen h-screen flex">
|
||||
<div className="h-screen min-w-[120px] md:min-w-[212px] overflow-scroll px-4 py-8 bg-[#f5f6f7] dark:bg-[#1F2A37]">
|
||||
<div className="h-screen min-w-[120px] md:min-w-[212px] overflow-scroll px-4 py-8 bg-neutral-100 dark:bg-gray-800">
|
||||
<h2 onClick={closeModal} className="flex gap-2 items-center text-sm md:text-base cursor-pointer mb-8 font-bold text-gray-800 dark:text-white">
|
||||
<IconBack className="dark:fill-gray-400" /> {title}
|
||||
</h2>
|
||||
@@ -58,7 +58,7 @@ const StyledSettingContainer: FC<PropsWithChildren<Props>> = ({
|
||||
</ul>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="bg-white w-full max-h-full overflow-auto p-8 dark:bg-[#384250]">
|
||||
<div className="bg-white w-full max-h-full overflow-auto p-8 dark:bg-gray-700">
|
||||
{nav && <h4 className="font-bold text-xl text-gray-600 mb-8 dark:text-gray-100">{nav.title}</h4>}
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FC } from "react";
|
||||
import Tippy, { TippyProps } from "@tippyjs/react";
|
||||
import clsx from "clsx";
|
||||
import { isMobile } from "../utils";
|
||||
|
||||
|
||||
const Triangle: FC<Pick<TippyProps, "placement">> = ({ placement }) => {
|
||||
@@ -23,6 +24,7 @@ const Tooltip: FC<Props> = ({ tip = "", placement = "right", delay = null, child
|
||||
|
||||
return (
|
||||
<Tippy
|
||||
disabled={isMobile()}
|
||||
offset={[0, 18]}
|
||||
duration={delay ? defaultDuration : 0}
|
||||
delay={delay ?? [150, 0]}
|
||||
|
||||
@@ -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 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 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 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");
|
||||
|
||||
@@ -14,7 +14,7 @@ const Version: FC<Props> = () => {
|
||||
<li>{t("build_time")}: {ts} <span className="text-gray-700 dark:text-gray-300">({dayjs(ts * 1000).fromNow()})</span></li>
|
||||
<li>GitHub:
|
||||
<strong className="font-bold">
|
||||
<a className="text-[#06b6d4] underline underline-offset-2" href="https://github.com/Privoce/vocechat-web/issues" target="_blank" rel="noopener noreferrer">vocechat-web/issues</a>
|
||||
<a className="text-primary-600 underline underline-offset-2" href="https://github.com/Privoce/vocechat-web/issues" target="_blank" rel="noopener noreferrer">vocechat-web/issues</a>
|
||||
</strong>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -23,6 +23,7 @@ import InviteModal from "../../../common/component/InviteModal";
|
||||
import LoadMore from "../LoadMore";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import GoBackNav from "../GoBackNav";
|
||||
type Props = {
|
||||
cid?: number;
|
||||
dropFiles?: File[];
|
||||
@@ -133,6 +134,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
|
||||
}
|
||||
header={
|
||||
<header className="h-14 px-5 flex items-center justify-center md:justify-between shadow-[inset_0_-1px_0_rgb(0_0_0_/_10%)]">
|
||||
<GoBackNav />
|
||||
<div className="flex items-center gap-1">
|
||||
<ChannelIcon personal={!is_public} />
|
||||
<span className="text-gray-800 dark:text-white">{name}</span>
|
||||
|
||||
@@ -11,6 +11,7 @@ import LoadMore from "../LoadMore";
|
||||
import { renderMessageFragment } from "../utils";
|
||||
import useMessageFeed from "../../../common/hook/useMessageFeed";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import GoBackNav from "../GoBackNav";
|
||||
type Props = {
|
||||
uid: number;
|
||||
dropFiles?: File[];
|
||||
@@ -64,7 +65,8 @@ const DMChat: FC<Props> = ({ uid = 0, dropFiles }) => {
|
||||
</ul>
|
||||
}
|
||||
header={
|
||||
<header className="box-border h-14 px-5 flex items-center justify-between shadow-[inset_0_-1px_0_rgb(0_0_0_/_10%)]">
|
||||
<header className="box-border h-14 px-5 flex items-center justify-center md:justify-between shadow-[inset_0_-1px_0_rgb(0_0_0_/_10%)]">
|
||||
<GoBackNav />
|
||||
<User interactive={false} uid={currUser.uid} />
|
||||
</header>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import IconArrow from '../../assets/icons/arrow.left.svg';
|
||||
// type Props = {}
|
||||
|
||||
const GoBackNav = () => {
|
||||
const navigate = useNavigate();
|
||||
const handleBack = () => {
|
||||
navigate(-1);
|
||||
};
|
||||
return (
|
||||
<button className='p-2 absolute left-2 md:hidden' onClick={handleBack}>
|
||||
<IconArrow className="dark:stroke-white w-6 h-6" />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default GoBackNav;
|
||||
@@ -21,7 +21,7 @@ const GuestBlankPlaceholder = () => {
|
||||
navigateTo("/login");
|
||||
};
|
||||
return (
|
||||
<section className="flex flex-col items-center bg-transparent">
|
||||
<section className="flex flex-col items-center">
|
||||
<h2 className="text-3xl text-gray-600 dark:text-gray-50 font-bold text-center">{t("welcome", { name: serverName })}</h2>
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-gray-400 dark:text-gray-200 my-3 text-sm">{t("guest_login_tip")}</span>
|
||||
|
||||
@@ -7,6 +7,7 @@ import Layout from "../Layout";
|
||||
import { renderMessageFragment } from "../utils";
|
||||
import LoadMore from "../LoadMore";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import GoBackNav from "../GoBackNav";
|
||||
|
||||
type Props = {
|
||||
cid?: number;
|
||||
@@ -41,6 +42,7 @@ export default function GuestChannelChat({ cid = 0 }: Props) {
|
||||
context="channel"
|
||||
header={
|
||||
<header className="h-14 px-5 flex items-center justify-center md:justify-between shadow-[inset_0_-1px_0_rgb(0_0_0_/_10%)]">
|
||||
<GoBackNav />
|
||||
<div className="flex items-center gap-1">
|
||||
<ChannelIcon personal={!is_public} />
|
||||
<span className="text-gray-800 dark:text-white">{name}</span>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { FC } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import Session from "./Session";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import LoginTip from "../Layout/LoginTip";
|
||||
export interface ChatSession {
|
||||
key: string;
|
||||
id: number;
|
||||
@@ -40,12 +41,15 @@ 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_-_38px)]">
|
||||
{sessions.map((s) => {
|
||||
const { key, id, mid = 0 } = s;
|
||||
return <Session key={key} id={id} mid={mid} />;
|
||||
})}
|
||||
</ul>
|
||||
<>
|
||||
<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} />;
|
||||
})}
|
||||
</ul>
|
||||
<LoginTip placement="session" />
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default SessionList;
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
// import { useEffect } from "react";
|
||||
import clsx from "clsx";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { resetAuthData } from "../../../app/slices/auth.data";
|
||||
import Button from "../../../common/component/styled/Button";
|
||||
import useLogout from "../../../common/hook/useLogout";
|
||||
// type Props = {};
|
||||
type Props = {
|
||||
placement?: "session" | "chat"
|
||||
};
|
||||
|
||||
const LoginTip = () => {
|
||||
const LoginTip = ({ placement = "chat" }: Props) => {
|
||||
const { t } = useTranslation("welcome");
|
||||
const { t: ct } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
@@ -20,8 +23,10 @@ const LoginTip = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between bg-slate-200/80 dark:bg-gray-800 rounded-lg w-full p-4">
|
||||
<span className="text-xs md:text-base text-[#98a2b3] dark:text-gray-100">
|
||||
<div className={clsx("flex items-center justify-between bg-slate-200/80 dark:bg-gray-800 rounded-lg w-full p-4 border border-solid border-gray-200 dark:border-gray-500",
|
||||
placement == "session" && "w-[96%] md:hidden fixed bottom-2 left-1/2 -translate-x-1/2"
|
||||
)}>
|
||||
<span className="text-xs md:text-base text-gray-400 dark:text-gray-100">
|
||||
<i className="mr-2 text-xs md:text-lg ">👋</i>
|
||||
{t("sign_in_tip")}
|
||||
</span>
|
||||
|
||||
@@ -111,7 +111,7 @@ const Layout: FC<Props> = ({
|
||||
<main className="h-full w-full flex items-start justify-between relative" ref={messagesContainer}>
|
||||
<div className="rounded-br-2xl w-full flex flex-col h-[calc(100vh_-_56px_-_18px)]">
|
||||
{children}
|
||||
<div className={`p-4 pt-0 ${selects ? "selecting" : ""}`}>
|
||||
<div className={`px-2 py-0 md:p-4 ${selects ? "selecting" : ""}`}>
|
||||
{readonly ? (
|
||||
<LoginTip />
|
||||
) : reachLimit ? (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { memo, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useMatch, useParams } from "react-router-dom";
|
||||
import clsx from "clsx";
|
||||
|
||||
import BlankPlaceholder from "../../common/component/BlankPlaceholder";
|
||||
@@ -13,9 +13,10 @@ import { useAppSelector } from "../../app/store";
|
||||
import GuestBlankPlaceholder from "./GuestBlankPlaceholder";
|
||||
import GuestChannelChat from "./GuestChannelChat";
|
||||
import GuestSessionList from "./GuestSessionList";
|
||||
import IconList from '../../assets/icons/list.svg';
|
||||
|
||||
function ChatPage() {
|
||||
const isHomePath = useMatch(`/`);
|
||||
const isChatHomePath = useMatch(`/chat`);
|
||||
const [sessionListVisible, setSessionListVisible] = useState(false);
|
||||
const [channelModalVisible, setChannelModalVisible] = useState(false);
|
||||
const [usersModalVisible, setUsersModalVisible] = useState(false);
|
||||
@@ -46,7 +47,7 @@ function ChatPage() {
|
||||
};
|
||||
// console.log("temp uid", tmpUid);
|
||||
const placeholderVisible = channel_id == 0 && user_id == 0;
|
||||
|
||||
const isMainPath = isHomePath || isChatHomePath;
|
||||
return (
|
||||
<>
|
||||
{channelModalVisible && (
|
||||
@@ -55,17 +56,16 @@ function ChatPage() {
|
||||
{usersModalVisible && <UsersModal closeModal={toggleUsersModalVisible} />}
|
||||
<div className={clsx(`flex h-full md:pt-2 md:pb-2.5 md:pr-1`, isGuest ? "guest-container md:px-1" : "md:pr-12")}>
|
||||
{sessionListVisible && <div onClick={toggleSessionList} className="z-30 fixed top-0 left-4 w-screen h-screen bg-black/50 transition-all backdrop-blur-sm"></div>}
|
||||
<div className={clsx("left-container flex-col md:rounded-l-2xl max-w-[250px] md:min-w-[268px] h-full shadow-[rgb(0_0_0_/_10%)_-1px_0px_0px_inset] fixed md:relative top-0 left-0 z-40 transition-all bg-white dark:!bg-[#1F2A37]", sessionListVisible ? "max-md:translate-x-0" : "max-md:-translate-x-full")}>
|
||||
<div className={clsx("left-container pb-14 md:pb-0 flex-col md:rounded-l-2xl w-full md:max-w-[250px] md:min-w-[268px] h-full shadow-[rgb(0_0_0_/_10%)_-1px_0px_0px_inset] bg-white dark:!bg-gray-800",
|
||||
isMainPath ? "flex" : "hidden md:flex"
|
||||
)}>
|
||||
<Server readonly={isGuest} />
|
||||
{isGuest ? <GuestSessionList /> : <SessionList tempSession={tmpSession} />}
|
||||
{sessionListVisible ? null : <button className="absolute top-2 -right-[52px] z-50 p-2 bg-none md:hidden" onClick={toggleSessionList}>
|
||||
<IconList className="dark:stroke-gray-300" />
|
||||
</button>}
|
||||
{isGuest && <footer className="text-xs text-gray-300 dark:text-gray-700 text-center">
|
||||
{isGuest && <footer className="hidden md:block text-xs text-gray-300 dark:text-gray-700 text-center">
|
||||
Host your own <a href="https://voce.chat" target="_blank" rel="noopener noreferrer" className="text-gray-400 dark:text-gray-600">voce.chat</a>
|
||||
</footer>}
|
||||
</div>
|
||||
<div className={`right-container md:rounded-r-2xl w-full ${placeholderVisible ? "h-full flex-center" : ""} bg-white dark:!bg-[#384250]`}>
|
||||
<div className={clsx(`right-container md:rounded-r-2xl w-full bg-white dark:!bg-gray-700`, placeholderVisible && "h-full flex-center", isMainPath && "hidden md:flex")}>
|
||||
{placeholderVisible && (isGuest ? <GuestBlankPlaceholder /> : <BlankPlaceholder />)}
|
||||
{channel_id !== 0 &&
|
||||
(isGuest ? (
|
||||
|
||||
@@ -118,7 +118,7 @@ function FavsPage() {
|
||||
removeFavorite(id);
|
||||
};
|
||||
return (
|
||||
<div className="h-screen flex bg-white mt-2 mr-6 mb-2.5 overflow-auto dark:bg-[#384250] rounded-2xl">
|
||||
<div className="h-screen flex bg-white mt-2 mr-6 mb-2.5 overflow-auto dark:bg-gray-700 rounded-2xl">
|
||||
<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 }) => {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { NavLink, useLocation, useMatch } from 'react-router-dom';
|
||||
import { useAppSelector } from '../../app/store';
|
||||
|
||||
import ChatIcon from "../../assets/icons/chat.svg";
|
||||
import UserIcon from "../../assets/icons/user.svg";
|
||||
|
||||
// type Props = {}
|
||||
|
||||
const MobileNavs = () => {
|
||||
const isHomePath = useMatch(`/`);
|
||||
const { pathname } = useLocation();
|
||||
const isChatHomePath = useMatch(`/chat`);
|
||||
const isDMChat = useMatch(`/chat/dm/:user_id`);
|
||||
const isChannelChat = useMatch(`/chat/channel/:channel_id`);
|
||||
const {
|
||||
ui: {
|
||||
rememberedNavs: { chat: chatPath, user: userPath }
|
||||
}
|
||||
} = useAppSelector((store) => {
|
||||
return {
|
||||
ui: store.ui,
|
||||
loginUid: store.authData.user?.uid,
|
||||
guest: store.authData.guest
|
||||
};
|
||||
});
|
||||
|
||||
const linkClass = `flex`;
|
||||
const isChatPage = isHomePath || pathname.startsWith("/chat");
|
||||
const isChattingPage = !!isDMChat || !!isChannelChat;
|
||||
console.log("rrr", isDMChat, isChannelChat);
|
||||
|
||||
// 有点绕
|
||||
const chatNav = isChatHomePath ? "/chat" : chatPath || "/chat";
|
||||
const userNav = userPath || "/users";
|
||||
return (
|
||||
<ul className={clsx('flex justify-around py-2 fixed bottom-0 left-0 w-full bg-gray-100 dark:bg-gray-800 md:hidden', isChattingPage && "hidden")}>
|
||||
<li>
|
||||
<NavLink
|
||||
className={() => `${linkClass}`}
|
||||
to={chatNav}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
const active = isActive || isChatPage;
|
||||
return <div className='flex flex-col gap-1 items-center'>
|
||||
<ChatIcon className={!active ? "fill-gray-600" : "fill-gray-400"} />
|
||||
<span className={clsx('text-xs', !active ? "text-gray-500" : "text-gray-400")}>Chats</span>
|
||||
</div>;
|
||||
}}
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink className={() => `${linkClass}`} to={userNav}>
|
||||
{({ isActive: active }) => {
|
||||
return <div className='flex flex-col gap-1 items-center'>
|
||||
<UserIcon className={!active ? "fill-gray-600" : "fill-gray-400"} />
|
||||
<span className={clsx('text-xs', !active ? "text-gray-500" : "text-gray-400")}>Contacts</span>
|
||||
</div>;
|
||||
}}
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
export default MobileNavs;
|
||||
@@ -14,7 +14,7 @@ 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";
|
||||
import { isMobile } from "../../common/utils";
|
||||
import MobileNavs from "./MobileNavs";
|
||||
|
||||
|
||||
function HomePage() {
|
||||
@@ -49,14 +49,13 @@ function HomePage() {
|
||||
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 />
|
||||
{!guest && <Notification />}
|
||||
<div className={`vocechat-container flex w-screen h-screen bg-[#e5e7eb] dark:bg-[#121926]`}>
|
||||
{!guest && (
|
||||
<div className={`h-full flex flex-col items-center relative w-16 bg-[#e5e7eb] dark:bg-[#121926] transition-all`}>
|
||||
<div className={`hidden md:flex h-full flex-col items-center relative w-16 bg-[#e5e7eb] dark:bg-[#121926] transition-all`}>
|
||||
{loginUid && <User uid={loginUid} />}
|
||||
<nav className="flex flex-col gap-1 px-3 py-6">
|
||||
<NavLink
|
||||
@@ -64,28 +63,28 @@ function HomePage() {
|
||||
to={chatNav}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
return <Tooltip disabled={mobile} tip={t("chat")}>
|
||||
return <Tooltip 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 disabled={mobile} tip={t("members")}>
|
||||
return <Tooltip 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 disabled={mobile} tip={t("favs")}>
|
||||
return <Tooltip 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 disabled={mobile} tip={t("files")}>
|
||||
return <Tooltip tip={t("files")}>
|
||||
<FolderIcon className={isActive ? "fill-white" : ""} />
|
||||
</Tooltip>;
|
||||
}}
|
||||
@@ -98,6 +97,7 @@ function HomePage() {
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
{!guest && <MobileNavs />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ const InvitePage: FC = () => {
|
||||
if (!valid) return <>invite token expires or invalid</>;
|
||||
|
||||
return (
|
||||
<div className="flex-center h-screen dark:bg-[#384250]">
|
||||
<div className="flex-center h-screen dark:bg-gray-700">
|
||||
<div className="py-8 px-10 shadow-md rounded-xl">
|
||||
<div className="flex-center flex-col pb-6">
|
||||
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="w-14 h-14 mb-7 rounded-full" />
|
||||
|
||||
@@ -124,7 +124,7 @@ export default function LoginPage() {
|
||||
|
||||
|
||||
return (
|
||||
<div className="flex-center h-screen dark:bg-[#384250]">
|
||||
<div className="flex-center h-screen dark:bg-gray-700">
|
||||
<div className="py-8 px-10 shadow-md rounded-xl">
|
||||
<div className="flex-center flex-col pb-6">
|
||||
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="w-14 h-14 mb-3 md:mb-7 rounded-full" />
|
||||
|
||||
@@ -35,10 +35,10 @@ const UpdateFrontendURL = ({ refreshInviteLink }: Props) => {
|
||||
}
|
||||
}, [isSuccess]);
|
||||
return (
|
||||
<div className="absolute left-1/2 -translate-x-1/2 bottom-8 border-2 border-solid border-[#67E3F9] bg-[#F5FEFF] rounded-lg px-2 py-3 flex justify-start gap-4">
|
||||
<div className="absolute left-1/2 -translate-x-1/2 bottom-8 border-2 border-solid border-primary-300 bg-primary-25 rounded-lg px-2 py-3 flex justify-start gap-4">
|
||||
<InfoIcon />
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<span className="text-sm text-[#0E7090] mb-1">{t("update_domain_tip")}</span>
|
||||
<span className="text-sm text-primary-700 mb-1">{t("update_domain_tip")}</span>
|
||||
<div className="w-[400px] rounded flex gap-2">
|
||||
<StyledInput type={"url"} className="!shadow-none !bg-transparent" placeholder="Frontend URL" value={frontUrl} onChange={handleUpdateUrl} />
|
||||
<StyledButton disabled={!frontUrl || isLoading} onClick={updateFrontUrl} className="small ">
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Outlet } from "react-router-dom";
|
||||
|
||||
export default function RegContainer() {
|
||||
return (
|
||||
<div className="flex-center h-screen dark:bg-[#384250]">
|
||||
<div className="flex-center h-screen dark:bg-gray-700">
|
||||
<div className="py-8 px-10 shadow-md rounded-xl">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
@@ -82,7 +82,7 @@ function ResourceManagement({ fileMessages }) {
|
||||
}, [view, filter]);
|
||||
|
||||
return (
|
||||
<div className="h-screen md: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-gray-700">
|
||||
<Search value={filter.name} updateSearchValue={handleUpdateSearch} />
|
||||
<div className="flex justify-between w-full px-4 py-5">
|
||||
<Filter filter={filter} updateFilter={updateFilter} />
|
||||
|
||||
@@ -76,7 +76,7 @@ const CreateAPIKeyModal = ({ closeModal, uid }: Props) => {
|
||||
</div>
|
||||
: <form ref={formRef} className="w-full flex flex-col gap-2 items-center" action="/">
|
||||
<div className="flex flex-col gap-1 w-full">
|
||||
<label htmlFor={"name"} className="text-sm text-[#6b7280]">Name</label>
|
||||
<label htmlFor={"name"} className="text-sm text-gray-500">Name</label>
|
||||
<Input name={"name"} required placeholder='Please input API Key name'></Input>
|
||||
</div>
|
||||
</form>}
|
||||
|
||||
@@ -79,11 +79,11 @@ const CreateModal = ({ closeModal }: Props) => {
|
||||
>
|
||||
<form ref={formRef} className="w-full flex flex-col gap-2" action="/">
|
||||
<div className="flex flex-col items-start gap-1 w-full">
|
||||
<label htmlFor={"name"} className="text-sm text-[#6b7280]">Name</label>
|
||||
<label htmlFor={"name"} className="text-sm text-gray-500">Name</label>
|
||||
<Input name={"name"} required placeholder='Please input bot name'></Input>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-1 w-full">
|
||||
<label htmlFor={"webhook_url"} className="text-sm text-[#6b7280]">Webhook URL (Optional)</label>
|
||||
<label htmlFor={"webhook_url"} className="text-sm text-gray-500">Webhook URL (Optional)</label>
|
||||
<Input name={"webhook_url"} type="url" placeholder='Please input webhook url'></Input>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -58,7 +58,7 @@ const WebhookModal = ({ uid, webhook, closeModal }: Props) => {
|
||||
}
|
||||
>
|
||||
<form ref={formRef} className="w-full flex flex-col gap-2" action="/">
|
||||
<label htmlFor={"webhook"} className="text-sm text-[#6b7280]">Webhook URL</label>
|
||||
<label htmlFor={"webhook"} className="text-sm text-gray-500">Webhook URL</label>
|
||||
<Input name={"webhook"} value={url} onChange={handleUrlChange} type="url"></Input>
|
||||
</form>
|
||||
</StyledModal>
|
||||
|
||||
@@ -8,6 +8,7 @@ import Profile from "../../common/component/Profile";
|
||||
|
||||
import BlankPlaceholder from "../../common/component/BlankPlaceholder";
|
||||
import useFilteredUsers from "../../common/hook/useFilteredUsers";
|
||||
import clsx from "clsx";
|
||||
|
||||
function UsersPage() {
|
||||
const dispatch = useDispatch();
|
||||
@@ -23,12 +24,14 @@ function UsersPage() {
|
||||
}, [pathname]);
|
||||
|
||||
if (!users) return null;
|
||||
|
||||
const isUserDetail = !!user_id;
|
||||
return (
|
||||
<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)]">
|
||||
<div className={clsx("flex h-full md:pt-2 md:pb-2.5 md:pr-12")}>
|
||||
<div className={clsx("md:rounded-l-2xl bg-white dark:bg-gray-800 relative flex flex-col w-full md:w-auto md:min-w-[268px] shadow-[inset_-1px_0px_0px_rgba(0,_0,_0,_0.1)]",
|
||||
isUserDetail && "hidden"
|
||||
)}>
|
||||
<Search input={input} updateInput={updateInput} />
|
||||
<div className="px-2 py-3 overflow-scroll">
|
||||
<div className="px-2 pt-3 pb-20 md:py-3 overflow-scroll">
|
||||
<nav className="flex flex-col md:gap-1">
|
||||
{users.map(({ uid }) => {
|
||||
return (
|
||||
@@ -40,8 +43,11 @@ function UsersPage() {
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<div className={`rounded-r-2xl bg-white w-full flex justify-center items-start ${!user_id ? "h-full items-center" : ""} dark:bg-[#384250]`}>
|
||||
{user_id ? <Profile uid={+user_id} /> : <BlankPlaceholder type="user" />}
|
||||
<div className={clsx(`md:rounded-r-2xl bg-white w-full flex justify-center items-start dark:bg-gray-700`,
|
||||
!user_id && "h-full items-center",
|
||||
!isUserDetail && "hidden md:flex"
|
||||
)}>
|
||||
{isUserDetail ? <Profile uid={+user_id} /> : <BlankPlaceholder type="user" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user