feat: setting for mobile friendly

This commit is contained in:
Tristan Yang
2023-02-17 09:22:59 +08:00
parent 0b07bbf440
commit 3a356b6311
38 changed files with 157 additions and 117 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ const PinList: FC<Props> = ({ id }: Props) => {
};
const noPins = pins.length == 0;
return (
<div className="p-4 drop-shadow-md overflow-y-scroll max-h-[90vh] min-w-[486px] rounded-xl bg-gray-50 dark:bg-gray-800">
<div className="p-4 drop-shadow-md overflow-y-scroll min-w-[320px] md:min-w-[486px] md:max-h-[90vh] rounded-xl bg-gray-50 dark:bg-gray-800">
<h4 className=" text-gray-600 dark:text-gray-400 mb-4 font-semibold">{t("pinned_msg")}({pins.length})</h4>
{noPins ? (
<div className="flex flex-col items-center gap-2 w-full p-4">
+2 -2
View File
@@ -23,7 +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";
import GoBackNav from "../../../common/component/GoBackNav";
type Props = {
cid?: number;
dropFiles?: File[];
@@ -170,7 +170,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
{hasMore ? (
<LoadMore pullUp={pullUp} pulling={pulling} />
) : (
<div className="pt-14 flex flex-col items-start gap-2">
<div className="pt-14 px-1 md:px-0 flex flex-col items-start gap-2">
<h2 className="font-bold text-4xl dark:text-white">{t("welcome_channel", { name })}</h2>
<p className="text-gray-600 dark:text-gray-300">{t("welcome_desc", { name })} </p>
{loginUser?.is_admin && (
+1 -1
View File
@@ -11,7 +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";
import GoBackNav from "../../../common/component/GoBackNav";
type Props = {
uid: number;
dropFiles?: File[];
-18
View File
@@ -1,18 +0,0 @@
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;
+1 -1
View File
@@ -7,7 +7,7 @@ import Layout from "../Layout";
import { renderMessageFragment } from "../utils";
import LoadMore from "../LoadMore";
import { useAppSelector } from "../../../app/store";
import GoBackNav from "../GoBackNav";
import GoBackNav from "../../../common/component/GoBackNav";
type Props = {
cid?: number;
@@ -8,7 +8,7 @@ const LicenseUpgradeTip = () => {
const { t } = useTranslation("chat");
const navigateTo = useNavigate();
const handleRedirect = () => {
navigateTo("/setting?nav=license");
navigateTo("/setting/license");
};
return (
+16 -4
View File
@@ -5,6 +5,7 @@ import { useAppSelector } from '../../app/store';
import ChatIcon from "../../assets/icons/chat.svg";
import UserIcon from "../../assets/icons/user.svg";
import SettingIcon from "../../assets/icons/setting.svg";
// type Props = {}
@@ -13,6 +14,7 @@ const MobileNavs = () => {
const { pathname } = useLocation();
const isChatHomePath = useMatch(`/chat`);
const isDMChat = useMatch(`/chat/dm/:user_id`);
// const isSettingPage = useMatch(`/setting`);
const isChannelChat = useMatch(`/chat/channel/:channel_id`);
const {
ui: {
@@ -44,8 +46,8 @@ const MobileNavs = () => {
{({ 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>
<ChatIcon className={!active ? "fill-gray-500" : "fill-primary-500"} />
<span className={clsx('text-xs', !active ? "text-gray-500" : "text-primary-500")}>Chats</span>
</div>;
}}
</NavLink>
@@ -54,8 +56,18 @@ const MobileNavs = () => {
<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>
<UserIcon className={!active ? "fill-gray-500" : "fill-primary-500"} />
<span className={clsx('text-xs', !active ? "text-gray-500" : "text-primary-500")}>Contacts</span>
</div>;
}}
</NavLink>
</li>
<li>
<NavLink className={() => `${linkClass}`} to={'/setting'}>
{({ isActive: active }) => {
return <div className='flex flex-col gap-1 items-center'>
<SettingIcon className={clsx("w-6 h-6", !active ? "fill-gray-500" : "fill-primary-500")} />
<span className={clsx('text-xs', !active ? "text-gray-500" : "text-primary-500")}>Settings</span>
</div>;
}}
</NavLink>
+1 -1
View File
@@ -14,7 +14,7 @@ const User: FC<Props> = ({ uid }) => {
return (
<div className="px-3 py-2.5 invisible md:visible">
<NavLink to={`/setting?nav=my_account&f=${pathname}`}>
<NavLink to={`/setting/my_account?f=${pathname}`}>
<div className="w-8 h-8">
<Avatar className=" object-cover w-full h-full rounded-full" width={32} height={32} src={user.avatar} name={user.name} />
</div>
+3 -2
View File
@@ -130,8 +130,9 @@ const PageRoutes = () => {
</LazyIt>
}
/>
<Route path="channel/:cid" element={<LazyIt><SettingChannelPage /></LazyIt>} />
<Route path="dm/:uid" element={<LazyIt><SettingDMPage /></LazyIt>} />
<Route path=":nav?" element={<LazyIt><SettingPage /></LazyIt>} />
<Route path="channel/:cid/:nav?" element={<LazyIt><SettingChannelPage /></LazyIt>} />
<Route path="dm/:uid/:nav?" element={<LazyIt><SettingDMPage /></LazyIt>} />
</Route>
<Route
index
+1 -1
View File
@@ -131,7 +131,7 @@ export default function LoginPage() {
<h2 className="font-semibold text-2xl text-gray-800 dark:text-white md:mb-2">{t("login.title")}</h2>
<span className="text-gray-400 dark:text-gray-100">{t("login.desc")}</span>
</div>
<form className="flex flex-col gap-5 w-[320px] md:min-w-[360px] " onSubmit={handleLogin}>
<form className="flex flex-col gap-5 w-80 md:min-w-[360px] " onSubmit={handleLogin}>
<Input
className="large"
name="email"
+1 -1
View File
@@ -105,7 +105,7 @@ const RegWithUsername: FC = () => {
visible to others in spaces you joined.
</span>
</div>
<form className="flex flex-col gap-5 min-w-[360px]" onSubmit={handleAuth}>
<form className="flex flex-col gap-5 w-80 md:min-w-[360px]" onSubmit={handleAuth}>
<Input
className="large"
name="username"
+4 -4
View File
@@ -103,12 +103,12 @@ export default function Reg() {
return (
<>
<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" />
<h2 className="font-semibold text-2xl text-gray-800 dark:text-white mb-2">{t("reg.title")}</h2>
<span className="text-gray-400 dark:text-gray-100">{t("reg.desc")}</span>
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="w-14 h-14 md:mb-7 rounded-full" />
<h2 className="font-semibold text-2xl text-gray-800 dark:text-white md:mb-2">{t("reg.title")}</h2>
<span className="hidden md:block text-gray-400 dark:text-gray-100">{t("reg.desc")}</span>
</div>
<form className="flex flex-col gap-5 min-w-[360px]" onSubmit={handleReg} autoSave={"false"} autoComplete={"true"}>
<form className="flex flex-col gap-5 w-80 md:min-w-[360px]" onSubmit={handleReg} autoSave={"false"} autoComplete={"true"}>
<Input
className="large"
name="email"
+1 -1
View File
@@ -2,7 +2,7 @@ import { Outlet } from "react-router-dom";
export default function RegContainer() {
return (
<div className="flex-center h-screen dark:bg-gray-700">
<div className="flex-center h-screen overflow-x-hidden overflow-y-auto dark:bg-gray-700">
<div className="py-8 px-10 shadow-md rounded-xl">
<Outlet />
</div>
+3 -3
View File
@@ -35,7 +35,7 @@ const APIDocument = () => {
👉 {t("api_doc.step_1")}
</h3>
{/* <div className="flex flex-col gap-1"> */}
<img className='border border-solid rounded-md border-gray-300 shadow-lg w-[85%]' src="https://s.voce.chat/web_client/assets/img/api.doc.step1.png" alt="step 1" />
<img className='border border-solid rounded-md border-gray-300 shadow-lg md:w-[85%]' src="https://s.voce.chat/web_client/assets/img/api.doc.step1.png" alt="step 1" />
{/* </div> */}
</div>
<div className="flex flex-col gap-2">
@@ -50,13 +50,13 @@ const APIDocument = () => {
<IconCopy onClick={handleCopy} className="absolute right-2 bottom-2 cursor-pointer" />
</p>
</div>
<img className='border border-solid rounded-md border-gray-300 shadow-lg w-[85%]' src="https://s.voce.chat/web_client/assets/img/api.doc.step2.jpg" alt="step 2" />
<img className='border border-solid rounded-md border-gray-300 shadow-lg md:w-[85%]' src="https://s.voce.chat/web_client/assets/img/api.doc.step2.jpg" alt="step 2" />
</div>
<div className="flex flex-col gap-2">
<h3 className='text-gray-700 dark:text-white text-lg flex items-center gap-2'>
👉 {t("api_doc.last")}
</h3>
<img className='border border-solid rounded-md border-gray-300 shadow-lg w-[85%]' src="https://s.voce.chat/web_client/assets/img/api.doc.step3.png" alt="step 3" />
<img className='border border-solid rounded-md border-gray-300 shadow-lg md:w-[85%]' src="https://s.voce.chat/web_client/assets/img/api.doc.step3.png" alt="step 3" />
</div>
</div>
+1 -1
View File
@@ -73,7 +73,7 @@ export default function BotConfig() {
</h2>
<p className='text-gray-500 dark:text-gray-400 text-xs'>{t("manage_desc")}</p>
</div>
<div className="w-fit overflow-hidden">
<div className="w-full md:w-fit overflow-auto md:overflow-hidden">
<table className="min-w-full table-auto">
<thead className="border-b dark:border-b-gray-500 bg-gray-50 dark:bg-gray-600">
<tr>
+4 -4
View File
@@ -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 md:w-[512px] bg-gray-100 dark:bg-gray-800 rounded-2xl">
<div className="md:p-6 flex flex-col items-center w-full md:w-[512px] md:bg-gray-100 md:dark:bg-gray-800 md: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 flex-col items-start md:flex-row justify-between mb-6">
<div className="w-full flex items-start 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 flex-col items-start md:flex-row justify-between mb-6">
<div className="w-full flex items-start 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 flex-col items-start md:flex-row justify-between mb-6">
<div className="w-full flex items-start 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>
+1 -1
View File
@@ -34,7 +34,7 @@ export default function Widget() {
<div className="text-gray-500 dark:text-white text-sm mt-5 mb-2">
{t('config')}:
</div>
<div className="w-[700px] border border-solid border-gray-300 dark:border-gray-400 rounded overflow-hidden">
<div className="w-full md:w-[700px] border border-solid border-gray-300 dark:border-gray-400 rounded overflow-auto md:overflow-hidden">
<table className="min-w-full table-auto">
<thead className="border-b bg-gray-50 dark:bg-gray-500">
<tr>
+1 -1
View File
@@ -77,7 +77,7 @@ export default function ConfigFirebase() {
client_email,
} = values as FirebaseConfig;
return (
<div className="setting-container">
<div className="setting-container max-md:w-full max-md:h-auto">
<StyledRadio
options={Object.values(Options)}
values={Object.keys(Options)}
+1 -1
View File
@@ -70,7 +70,7 @@ export default function Logins() {
const valuesChanged = clientIdChanged || changed || githubChanged;
return (
<div className="setting-container">
<div className="setting-container max-md:w-full max-md:h-auto">
<div className="inputs">
<div className="input">
<div className="row">
+1 -1
View File
@@ -53,7 +53,7 @@ export default function ConfigSMTP() {
if (!values) return null;
const { host, port, from, username, password, enabled = false } = values as SMTPConfig;
return (
<div className="setting-container">
<div className="setting-container max-md:w-full">
<div className="inputs">
<div className="input row">
<Label className="dark:text-gray-200">{t("enable")}</Label>
+4 -4
View File
@@ -1,5 +1,5 @@
import { useState } from "react";
import { useNavigate, useSearchParams } from "react-router-dom";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import StyledSettingContainer from "../../common/component/StyledSettingContainer";
import useNavs from "./navs";
import LogoutConfirmModal from "./LogoutConfirmModal";
@@ -12,7 +12,7 @@ export default function Setting() {
const [searchParams] = useSearchParams();
const navs = useNavs();
const flattenNaves = navs.map(({ items }) => items).flat();
const navKey = searchParams.get("nav");
const { nav: navKey } = useParams();;
const [logoutConfirm, setLogoutConfirm] = useState(false);
const navigateTo = useNavigate();
pageFrom = pageFrom ? pageFrom : searchParams.get("f") || "/";
@@ -25,7 +25,7 @@ export default function Setting() {
setLogoutConfirm((prev) => !prev);
};
const currNav = flattenNaves.find((n) => n.name == navKey) || flattenNaves[0];
const currNav = flattenNaves.find((n) => n.name == navKey);
return (
<>
@@ -36,7 +36,7 @@ export default function Setting() {
navs={navs}
dangers={[{ title: t("action.logout"), handler: toggleLogoutConfirm }]}
>
{currNav.component}
{navKey ? currNav?.component : null}
</StyledSettingContainer>
{logoutConfirm && <LogoutConfirmModal closeModal={toggleLogoutConfirm} />}
</>
+4 -4
View File
@@ -11,7 +11,7 @@ let from: string = "";
export default function ChannelSetting() {
const { t } = useTranslation("setting");
const { cid = 0 } = useParams();
const { cid = 0, nav: navKey } = useParams();
const { loginUser, channel } = useAppSelector((store) => {
return {
loginUser: store.authData.user,
@@ -26,7 +26,6 @@ export default function ChannelSetting() {
return items;
})
.flat();
const navKey = searchParams.get("nav");
from = from ? from : searchParams.get("f") || "/";
const [deleteConfirm, setDeleteConfirm] = useState(false);
const [leaveConfirm, setLeaveConfirm] = useState(false);
@@ -41,13 +40,14 @@ export default function ChannelSetting() {
setLeaveConfirm((prev) => !prev);
};
if (!cid) return null;
const currNav = flattenNavs.find((n) => n.name == navKey) || flattenNavs[0];
const currNav = flattenNavs.find((n) => n.name == navKey);
const canDelete = loginUser?.is_admin || channel?.owner == loginUser?.uid;
const canLeave = !channel?.is_public;
return (
<>
<StyledSettingContainer
pathPrefix={`/setting/channel/${cid}`}
nav={currNav}
closeModal={close}
title="Channel Setting"
@@ -63,7 +63,7 @@ export default function ChannelSetting() {
}
]}
>
{currNav.component}
{navKey ? currNav?.component : null}
</StyledSettingContainer>
{deleteConfirm && <DeleteConfirmModal closeModal={toggleDeleteConfirm} id={+cid} />}
{leaveConfirm && <LeaveChannel closeModal={toggleLeaveConfirm} id={+cid} />}
+8 -8
View File
@@ -11,7 +11,7 @@ let from: string = "";
export default function DMSetting() {
// const { t } = useTranslation("setting");
const { t: ct } = useTranslation();
const { uid = 0 } = useParams();
const { uid = 0, nav: navKey } = useParams();
const { loginUser } = useAppSelector((store) => {
return {
loginUser: store.authData.user,
@@ -26,7 +26,6 @@ export default function DMSetting() {
return items;
})
.flat();
const navKey = searchParams.get("nav");
from = from ? from : searchParams.get("f") || "/";
const [deleteConfirm, setDeleteConfirm] = useState(false);
const close = () => {
@@ -37,24 +36,25 @@ export default function DMSetting() {
setDeleteConfirm((prev) => !prev);
};
if (!uid) return null;
const currNav = flattenNavs.find((n) => n.name == navKey) || flattenNavs[0];
const currNav = flattenNavs.find((n) => n.name == navKey);
const canDelete = loginUser?.is_admin;
return (
<>
<StyledSettingContainer
pathPrefix={`/setting/dm/${uid}`}
nav={currNav}
closeModal={close}
title="DM Setting"
navs={navs}
dangers={[
canDelete && {
dangers={
canDelete ? [{
title: ct("action.remove_user"),
handler: toggleDeleteConfirm
}
]}
}] : []
}
>
{currNav.component}
{navKey ? currNav?.component : null}
</StyledSettingContainer>
{deleteConfirm && <DeleteConfirmModal closeModal={toggleDeleteConfirm} id={+uid} />}
</>
+2
View File
@@ -9,6 +9,7 @@ import Profile from "../../common/component/Profile";
import BlankPlaceholder from "../../common/component/BlankPlaceholder";
import useFilteredUsers from "../../common/hook/useFilteredUsers";
import clsx from "clsx";
import GoBackNav from "../../common/component/GoBackNav";
function UsersPage() {
const dispatch = useDispatch();
@@ -48,6 +49,7 @@ function UsersPage() {
!isUserDetail && "hidden md:flex"
)}>
{isUserDetail ? <Profile uid={+user_id} /> : <BlankPlaceholder type="user" />}
<GoBackNav />
</div>
</div>
);