feat: darkmode draft version

This commit is contained in:
Tristan Yang
2023-02-06 07:35:31 +08:00
parent b4c5d5cb86
commit 07ddfbf12b
111 changed files with 885 additions and 2102 deletions
+2 -2
View File
@@ -49,8 +49,8 @@ const AutoDeleteMessages = ({ id, type = "channel" }: Props) => {
return (
<section className="max-w-[512px] h-full relative">
<div className="text-sm">
<h2 >{t("title")}</h2>
<p className="text-gray-500">{t("desc")}</p>
<h2 className='dark:text-white' >{t("title")}</h2>
<p className="text-gray-500 dark:text-gray-400">{t("desc")}</p>
</div>
<div className="mt-4">
<StyledRadio
+2 -2
View File
@@ -48,11 +48,11 @@ const AvatarUploader: FC<Props> = ({
<Avatar width={size} height={size} type={type} src={url} name={name} className={`${className} object-cover w-full h-full`} />
{!disabled && (
<>
<div className="flex-center flex-col whitespace-nowrap hidden group-hover:flex p-1 absolute top-0 left-0 right-0 bottom-0 bg-black/50 text-white font-bold text-xs">
<div className="flex-center flex-col whitespace-nowrap hidden group-hover:flex p-1 absolute inset-0 bg-black/50 text-white font-bold text-xs">
{uploading ? t("status.uploading") : t("action.change_avatar")}
</div>
<input
className="opacity-0 absolute top-0 left-0 right-0 bottom-0 block cursor-pointer"
className="opacity-0 absolute inset-0 block cursor-pointer"
multiple={false}
onChange={handleUpload}
type="file"
+5 -5
View File
@@ -14,9 +14,9 @@ interface Props {
type?: "chat" | "user";
}
const classes = {
box: "w-[150px] md:w-[200px] h-[150px] md:h-[200px] cursor-pointer bg-[#f9fafb] rounded-3xl flex-center flex-col gap-4",
box: "w-[150px] md:w-[200px] h-[150px] md:h-[200px] cursor-pointer bg-[#f9fafb] dark:bg-gray-800 rounded-3xl flex-center flex-col gap-4",
boxIcon: "w-10 h-10",
boxTip: "px-5 text-sm text-[#475467] font-bold text-center"
boxTip: "px-5 text-sm text-[#475467] dark:text-gray-100 font-bold text-center"
};
const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
const { t } = useTranslation("welcome");
@@ -38,14 +38,14 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
const chatHandler = type == "chat" ? toggleChannelModalVisible : toggleUserListVisible;
return (
<>
<div className="flex flex-col gap-8 -mt-[50px]">
<div className="flex flex-col gap-8 -mt-[50px] dark:bg-[#384250]">
<div className="flex flex-col gap-2 items-center">
<h2 className="text-3xl text-[#344054] font-bold">{t("title", { name: server.name })}</h2>
<h2 className="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">
{t("desc")}
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 grid-rows-2 gap-2 md:gap-6 mt-auto">
<div className="grid grid-cols-1 md:grid-cols-2 grid-rows-2 gap-2 md:gap-6 m-auto">
<div className={classes.box} onClick={toggleInviteModalVisible}>
<IconInvite className={classes.boxIcon} />
<div className={classes.boxTip}>{t("invite")}</div>
+21 -20
View File
@@ -5,7 +5,6 @@ import Modal from "../Modal";
import Button from "../styled/Button";
import ChannelIcon from "../ChannelIcon";
import User from "../User";
import StyledWrapper from "./styled";
import StyledCheckbox from "../styled/Checkbox";
import StyledToggle from "../../component/styled/Toggle";
import useFilteredUsers from "../../hook/useFilteredUsers";
@@ -13,6 +12,7 @@ import { useCreateChannelMutation } from "../../../app/services/channel";
import { useAppSelector } from "../../../app/store";
import { CreateChannelDTO } from "../../../types/channel";
import { useTranslation } from "react-i18next";
import clsx from "clsx";
interface Props {
personal?: boolean;
@@ -94,18 +94,19 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
return (
<Modal>
<StyledWrapper>
<div className="flex max-h-[402px] bg-white dark:bg-gray-800 drop-shadow rounded-lg">
{!is_public && (
<div className="left">
<div className="search">
<div className="w-[260px] shadow-[inset_-1px_0px_0px_rgba(0,_0,_0,_0.1)]">
<div className="sticky top-0 z-[99] rounded-tl-lg shadow-[0px_1px_0px_rgba(0,_0,_0,_0.1)] p-2 w-[calc(100%_-_1px)]">
<input
className="outline-none p-2 text-sm w-full bg-transparent dark:text-white"
value={input}
onChange={handleInputChange}
placeholder={t("search_user_placeholder")}
/>
</div>
{users && (
<ul className="users">
<ul className="flex flex-col overflow-y-scroll overflow-x-hidden h-[calc(100%_-_52px_-_10px)]">
{users.map((u) => {
const { uid } = u;
const checked = members ? members.includes(uid) : false;
@@ -113,7 +114,7 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
<li
key={uid}
data-uid={uid}
className="user"
className="cursor-pointer flex items-center px-4 rounded hover:bg-gray-500/10"
onClick={loginUid == uid ? undefined : toggleCheckMember}
>
<StyledCheckbox
@@ -131,38 +132,38 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
)}
</div>
)}
<div className={`right ${!is_public ? "private" : ""}`}>
<h3 className="title">{t("create_channel")}</h3>
<p className="desc normal">
<div className={clsx(`min-w-[400px] flex flex-col items-center p-8 box-border`, !is_public && "w-[344px] justify-evenly")}>
<h3 className="font-semibold text-xl text-gray-600 mb-4 dark:text-white">{t("create_channel")}</h3>
<p className="text-gray-400 mb-12 text-sm font-normal">
{!is_public
? t("create_private_channel_desc")
: t("create_channel_desc")}
</p>
<div className="name">
<span className="label normal">{t("channel_name")}</span>
<div className="input">
<input onChange={handleNameInput} value={name} placeholder="new channel" />
<ChannelIcon personal={!is_public} className="icon" />
<div className="w-full flex flex-col justify-start gap-2 mb-8.5">
<span className="text-gray-400 text-sm font-normal">{t("channel_name")}</span>
<div className="relative">
<input className="text-gray-600 rounded p-2 pl-9 border border-solid border-gray-300 w-full" onChange={handleNameInput} value={name} placeholder="new channel" />
<ChannelIcon personal={!is_public} className="absolute left-2 top-1/2 -translate-y-1/2" />
</div>
</div>
<div className="private">
<span className="txt normal">{t("private_channel")}</span>
<div className="w-full flex items-center justify-between mb-12.5">
<span className="text-gray-400 text-sm">{t("private_channel")}</span>
<StyledToggle
data-checked={!is_public}
data-disabled={!loginUser?.is_admin}
onClick={handleToggle}
/>
</div>
<div className="btns">
<Button onClick={closeModal} className="normal cancel">
<div className="w-full flex gap-4 items-center justify-end">
<Button onClick={closeModal} className="text-sm cancel">
{t("action.cancel", { ns: "common" })}
</Button>
<Button disabled={isLoading} onClick={handleCreate} className="normal">
<Button disabled={isLoading} onClick={handleCreate} className="text-sm">
{t("action.create", { ns: "common" })}
</Button>
</div>
</div>
</StyledWrapper>
</div>
</Modal>
);
};
@@ -1,136 +0,0 @@
import styled from "styled-components";
const StyledWrapper = styled.div`
display: flex;
max-height: 402px;
background: #fff;
box-shadow: 0px 25px 50px rgba(31, 41, 55, 0.25);
border-radius: var(--br);
transition: all 0.5s ease;
.left {
width: 260px;
box-shadow: inset -1px 0px 0px rgba(0, 0, 0, 0.1);
.search {
position: sticky;
top: 0;
z-index: 99;
background: #fff;
border-top-left-radius: var(--br);
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
padding: 8px;
width: calc(100% - 1px);
input {
outline: none;
width: -webkit-fill-available;
padding: 8px;
font-size: 14px;
line-height: 20px;
border: none;
}
}
.users {
display: flex;
flex-direction: column;
height: calc(100% - 52px - 10px);
overflow-y: scroll;
overflow-x: hidden;
.user {
cursor: pointer;
display: flex;
align-items: center;
padding: 0 16px;
width: -webkit-fill-available;
border-radius: 4px;
&:hover {
background: rgba(116, 127, 141, 0.1);
}
> div {
width: 100%;
}
}
}
}
.right {
min-width: 400px;
display: flex;
flex-direction: column;
align-items: center;
padding: 32px;
box-sizing: border-box;
&.private {
width: 344px;
justify-content: space-evenly;
.desc {
text-align: center;
}
}
> .title {
font-weight: 600;
font-size: 20px;
line-height: 30px;
color: #374151;
margin-bottom: 16px;
}
.desc {
font-weight: normal;
color: #6b7280;
margin-bottom: 48px;
}
.name {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 8px;
margin-bottom: 34px;
.label {
font-weight: 600;
color: #6b7280;
}
.input {
position: relative;
input {
width: -webkit-fill-available;
border: 1px solid #e5e7eb;
box-shadow: 0px 1px 2px rgba(31, 41, 55, 0.08);
border-radius: 4px;
padding: 8px;
color: #78787c;
padding-left: 36px;
}
.icon {
position: absolute;
left: 8px;
top: 50%;
transform: translateY(-50%);
}
}
}
.private {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 50px;
.txt {
font-weight: 600;
color: #6b7280;
}
input {
cursor: pointer;
}
}
.btns {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 16px;
}
.normal {
font-size: 14px;
line-height: 20px;
}
}
`;
export default StyledWrapper;
+2 -2
View File
@@ -4,8 +4,8 @@ interface Props {
}
const Divider: FC<Props> = ({ content }) => {
return <div className="relative border-none h-[1px] bg-[#e3e5e8] 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 font-semibold bg-white">{content}</span>
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>
</div>;
};
+2 -2
View File
@@ -15,14 +15,14 @@ const InviteLink: FC<Props> = () => {
return (
<div className="flex flex-col items-start pb-8">
<span className="font-semibold text-sm mb-2 text-gray-500">{t("share_invite_link")}</span>
<span className="font-semibold text-sm mb-2 text-gray-500 dark:text-gray-50">{t("share_invite_link")}</span>
<div className="w-[512px] mb-3 relative">
<Input readOnly className={"large !pr-16"} placeholder="Generating" value={link} />
<Button onClick={copyLink} className="ghost small border_less absolute right-1 top-1/2 -translate-y-1/2">
{linkCopied ? "Copied" : t("action.copy", { ns: "common" })}
</Button>
</div>
<span className="text-xs text-gray-600">{t("invite_link_expire")}</span>
<span className="text-xs text-gray-600 dark:text-gray-100">{t("invite_link_expire")}</span>
<div className="w-44 h-44 my-2">
<QRCode link={link} />
</div>
@@ -49,7 +49,7 @@ const InviteByEmail: FC<Props> = ({ cid }) => {
return (
<div className="pt-4">
<div className="flex flex-col gap-4 mb-6">
<label className="text-sm text-gray-400" htmlFor="">{t("invite_by_email")}</label>
<label className="text-sm text-gray-400 dark:text-gray-100" htmlFor="">{t("invite_by_email")}</label>
<div className="relative flex items-center gap-2">
<form ref={formRef} action="/" className="w-full">
<Input
@@ -69,7 +69,7 @@ const InviteByEmail: FC<Props> = ({ cid }) => {
</div>
</div>
<div className="flex flex-col gap-2 mb-3">
<label className="text-sm text-gray-400" htmlFor="">{t("send_invite_link")}</label>
<label className="text-sm text-gray-400 dark:text-gray-100" htmlFor="">{t("send_invite_link")}</label>
<div className="relative flex items-center gap-2">
<Input readOnly className="!pr-[50px]" placeholder="Generating" value={link} />
<button className="absolute right-1 top-1/2 -translate-y-1/2 pr-2 text-sm text-primary-400 hover:text-primary-600" onClick={copyLink}>
@@ -80,7 +80,7 @@ const InviteByEmail: FC<Props> = ({ cid }) => {
<div className="w-44 h-44 my-2">
{!generating && <QRCode link={link} />}
</div>
<div className="text-xs text-gray-600">
<div className="text-xs text-gray-600 dark:text-gray-200">
{t("invite_link_expire")}
<button disabled={generating} className="text-primary-400 ml-1" onClick={() => generateNewLink()}>
{t("generate_new_link")}
+3 -3
View File
@@ -25,10 +25,10 @@ const InviteModal: FC<Props> = ({ type = "server", cid, title = "", closeModal }
const finalTitle = type == "server" ? server.name : `#${title || channel?.name}`;
return (
<Modal>
<div className="flex flex-col bg-white rounded-lg p-4 min-w-[408px]">
<h2 className="flex items-center justify-between text-lg text-gray-700 ">
<div className="flex flex-col bg-white dark:bg-gray-900 rounded-lg p-4 min-w-[408px]">
<h2 className="flex items-center justify-between text-lg text-gray-700 dark:text-gray-50 ">
{t("invite_title", { name: finalTitle })}
<CloseIcon className="cursor-pointer" onClick={closeModal} />
<CloseIcon className="cursor-pointer dark:fill-white" onClick={closeModal} />
</h2>
{!channel?.is_public && <AddMembers cid={cid} closeModal={closeModal} />}
<InviteByEmail cid={channel?.is_public ? undefined : cid} />
+1 -1
View File
@@ -30,7 +30,7 @@ const Loading: FC<Props> = ({ reload = false, fullscreen = false }) => {
}, [reload]);
return (
<div className={clsx("w-full h-full flex-center flex-col gap-4", fullscreen ? "w-screen h-screen" : "")}>
<div className={clsx("w-full h-full flex-center flex-col gap-4 dark:bg-gray-800/80", fullscreen ? "w-screen h-screen" : "")}>
<Ring size={40} lineWeight={5} speed={2} color="black" />
<Button className={clsx(`danger`, reloadVisible ? "visible" : "invisible")} onClick={handleReload}>
Reload
+17 -18
View File
@@ -4,7 +4,6 @@ import { hideAll } from "tippy.js";
import toast from "react-hot-toast";
import { useUpdateUserMutation } from "../../../app/services/user";
import User from "../User";
import StyledWrapper from "./styled";
import StyledMenu from "../styled/Menu";
import InviteLink from "../InviteLink";
import moreIcon from "../../../assets/icons/more.svg?url";
@@ -54,16 +53,16 @@ const ManageMembers: FC<Props> = ({ cid }) => {
const channel = cid ? channels.byId[cid] : null;
const uids = channel ? (channel.is_public ? users.ids : channel.members) : users.ids;
return (
<StyledWrapper>
<section className="flex flex-col w-full">
{loginUser?.is_admin && <InviteLink />}
<div className="intro">
<h4 className="title">{t("manage_members")}</h4>
<p className="desc">
<div className="flex flex-col mb-10">
<h4 className="font-bold text-gray-600 dark:text-white">{t("manage_members")}</h4>
<p className="text-gray-500 dark:text-gray-100 text-xs">
{t("manage_tip")}
</p>
</div>
<ul className="members">
<ul className="flex flex-col gap-1 w-[512px] mb-44">
{uids.map((uid) => {
const currUser = users.byId[uid];
if (!currUser) return null;
@@ -75,18 +74,18 @@ const ManageMembers: FC<Props> = ({ cid }) => {
const canRemoveFromChannel =
channel && channel.owner == loginUser?.uid && loginUser?.uid != uid;
return (
<li key={uid} className="member">
<div className="left">
<li key={uid} className="w-full flex items-center justify-between px-3 py-2 rounded-md hover:bg-slate-50 dark:hover:bg-gray-800">
<div className="flex gap-4">
<User compact uid={uid} interactive={false} />
<div className="info">
<span className="name">
<div className="flex flex-col">
<span className="font-bold text-sm text-gray-600 dark:text-white flex items-center gap-1">
{name} {owner && <IconOwner />}
</span>
<span className="email">{email}</span>
<span className="text-xs text-gray-500 dark:text-slate-50">{email}</span>
</div>
</div>
<div className="right">
<span className="role">
<div className="flex items-center gap-7">
<span className="text-xs text-right text-gray-500 dark:text-slate-100 flex items-center gap-1">
{is_admin ? t("admin") : t("user")}
{switchRoleVisible && (
<Tippy
@@ -120,7 +119,7 @@ const ManageMembers: FC<Props> = ({ cid }) => {
</StyledMenu>
}
>
<IconArrowDown className="icon" />
<IconArrowDown className="cursor-pointer dark:fill-slate-50" />
</Tippy>
)}
</span>
@@ -130,7 +129,7 @@ const ManageMembers: FC<Props> = ({ cid }) => {
placement="right-start"
trigger="click"
content={
<StyledMenu className="menu">
<StyledMenu className="min-w-30">
{email && (
<li className="item" onClick={copyEmail.bind(null, email)}>
{ct("action.copy_email")}
@@ -149,8 +148,8 @@ const ManageMembers: FC<Props> = ({ cid }) => {
</StyledMenu>
}
>
<div className="opts">
<img className="dots" src={moreIcon} alt="dots icon" />
<div className="relative w-6 h-6">
<img className="cursor-pointer" src={moreIcon} alt="dots icon" />
</div>
</Tippy>
)}
@@ -159,7 +158,7 @@ const ManageMembers: FC<Props> = ({ cid }) => {
);
})}
</ul>
</StyledWrapper>
</section>
);
};
export default ManageMembers;
@@ -1,102 +0,0 @@
import styled from "styled-components";
const StyledWrapper = styled.section`
display: flex;
flex-direction: column;
width: 100%;
.intro {
display: flex;
flex-direction: column;
margin-bottom: 40px;
.title {
font-weight: bold;
font-size: 16px;
line-height: 24px;
color: #374151;
}
.desc {
font-weight: normal;
font-size: 12px;
line-height: 18px;
color: #616161;
}
}
.members {
display: flex;
flex-direction: column;
gap: 4px;
width: 512px;
margin-bottom: 176px;
.member {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
border-radius: var(--br);
&:hover {
background: #f9fafb;
}
.left {
display: flex;
gap: 8px;
.info {
display: flex;
flex-direction: column;
.name {
font-weight: bold;
font-size: 14px;
line-height: 20px;
color: #52525b;
display: flex;
align-items: center;
gap: 4px;
}
.email {
font-weight: normal;
font-size: 12px;
line-height: 18px;
color: #52525b;
}
}
}
.right {
display: flex;
align-items: center;
gap: 28px;
.role {
font-weight: 500;
font-size: 12px;
line-height: 18px;
text-align: right;
color: #616161;
display: flex;
align-items: center;
gap: 4px;
> .icon {
cursor: pointer;
}
/* override */
.menu {
min-width: 120px;
.item .icon {
width: 16px;
height: 12px;
}
}
}
.opts {
position: relative;
width: 24px;
height: 24px;
.dots {
cursor: pointer;
}
.menu {
position: absolute;
}
}
}
}
}
`;
export default StyledWrapper;
@@ -5,10 +5,9 @@ import "prismjs/themes/prism.css";
import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css";
import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
import StyledWrapper from "./styled";
import useUploadFile from "../../hook/useUploadFile";
import Button from "../styled/Button";
type Props = {
updateDraft?: (draft: string) => void;
initialValue: string;
@@ -62,7 +61,7 @@ const MarkdownEditor: FC<Props> = ({
}
};
return (
<StyledWrapper className="input">
<div className="input md-editor">
<Editor
initialValue={initialValue}
plugins={[codeSyntaxHighlight]}
@@ -78,7 +77,7 @@ const MarkdownEditor: FC<Props> = ({
<Button className="send small" onClick={send}>
Send
</Button>
</StyledWrapper>
</div>
);
};
export default MarkdownEditor;
@@ -1,40 +0,0 @@
import styled from "styled-components";
const StyledWrapper = styled.div`
position: relative;
width: 100%;
width: -webkit-fill-available;
margin-top: 16px;
> div {
transition: height 0.5s ease 0s;
}
.toastui-editor-defaultUI {
border-bottom: none;
border-radius: 0;
border-top: 1px solid #d0d5dd;
border-left: none;
border-right: none;
}
.toastui-editor {
padding: 16px 0;
[contenteditable="true"] {
padding: 0;
}
}
.toastui-editor-md-preview {
padding-top: 16px;
.toastui-editor-contents {
padding: 0;
}
}
.toastui-editor-toolbar {
display: none;
}
.send {
position: absolute;
bottom: 15px;
right: 15px;
}
`;
export default StyledWrapper;
+23 -53
View File
@@ -1,6 +1,5 @@
import { useState, useRef, FC } from "react";
import { useDispatch } from "react-redux";
import styled from "styled-components";
import Tippy from "@tippyjs/react";
import { hideAll } from "tippy.js";
import toast from "react-hot-toast";
@@ -21,47 +20,7 @@ import IconDelete from "../../../assets/icons/delete.svg";
import moreIcon from "../../../assets/icons/more.svg?url";
import useMessageOperation from "./useMessageOperation";
import { useTranslation } from "react-i18next";
const StyledCmds = styled.ul`
z-index: 999;
position: absolute;
right: 10px;
top: 0;
transform: translateY(-50%);
display: flex;
align-items: center;
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 6px;
background-color: #fff;
visibility: hidden;
&.visible {
visibility: visible;
}
.cmd {
display: flex;
cursor: pointer;
padding: 4px;
&:hover {
background-color: #f3f4f6;
}
img,
svg {
width: 24px;
height: 24px;
}
&.fav {
svg path {
fill: #667085;
}
}
}
> .picker {
position: absolute;
left: -10px;
top: 0;
transform: translateX(-100%);
}
`;
import clsx from "clsx";
type Props = {
context: "user" | "channel";
contextId: number;
@@ -124,9 +83,20 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
}
};
// const StyledCmds = styled.ul`
// > .picker {
// position: absolute;
// left: -10px;
// top: 0;
// transform: translateX(-100%);
// }
// `;
return (
<>
<StyledCmds ref={cmdsRef} className={`cmds ${tippyVisible ? "visible" : ""}`}>
<ul
ref={cmdsRef}
className={clsx(`cmds bg-white dark:bg-gray-900 rounded-md z-[999] absolute right-2.5 top-0 -translate-y-1/2 flex items-center border border-solid border-black/10 invisible`, tippyVisible && '!visible')}>
<Tippy
onShow={handleTippyVisible.bind(null, true)}
onHide={handleTippyVisible.bind(null, false)}
@@ -135,29 +105,29 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
trigger="click"
content={<ReactionPicker mid={mid} hidePicker={hideAll} />}
>
<li className="cmd">
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800">
<Tooltip placement="top" tip={t("action.add_reaction")}>
<img src={reactIcon} className="toggler" alt="icon emoji" />
<img src={reactIcon} className="toggler w-6 h-6" alt="icon emoji" />
</Tooltip>
</li>
</Tippy>
{canEdit && (
<li className="cmd" onClick={toggleEditMessage}>
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800" onClick={toggleEditMessage}>
<Tooltip placement="top" tip={t("action.edit")}>
<img src={editIcon} alt="icon edit" />
<img src={editIcon} className="w-6 h-6" alt="icon edit" />
</Tooltip>
</li>
)}
{canReply && (
<li className="cmd" onClick={handleReply}>
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800" onClick={handleReply}>
<Tooltip placement="top" tip={t("action.reply")}>
<img src={replyIcon} alt="icon reply" />
<img src={replyIcon} className="w-6 h-6" alt="icon reply" />
</Tooltip>
</li>
)}
<li className="cmd fav" onClick={handleAddFav}>
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800" onClick={handleAddFav}>
<Tooltip placement="top" tip={t("action.add_to_fav")}>
<IconBookmark />
<IconBookmark className="fill-[#667085] w-6 h-6" />
</Tooltip>
</li>
<Tippy
@@ -196,13 +166,13 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
/>
}
>
<li className="cmd">
<li className="flex cursor-pointer p-1 hover:bg-[#f3f4f6] dark:hover:bg-gray-800">
<Tooltip placement="top" tip={t("more")}>
<img src={moreIcon} alt="icon more" />
</Tooltip>
</li>
</Tippy>
</StyledCmds>
</ul>
{PinModal}
{ForwardModal}
{DeleteModal}
+2 -11
View File
@@ -1,17 +1,8 @@
// import { FC, ReactNode } from "react";
import Tippy from "@tippyjs/react";
import styled from "styled-components";
import Profile from "../Profile";
import { useAppSelector } from "../../../app/store";
const Styled = styled.span`
padding: 0 2px;
color: #1fe1f9;
&.clickable {
cursor: pointer;
}
`;
interface Props {
uid: number;
popover?: boolean;
@@ -24,7 +15,7 @@ const Mention = ({ uid, popover = true, cid, textOnly = false }: Props) => {
const user = usersData[uid];
if (!user) return null;
if (textOnly) return <>{`@${user.name}`}</>;
if (!popover) return <Styled>{`@${user.name}`}</Styled>;
if (!popover) return <span className="px-0.5 text-primary-400">{`@${user.name}`}</span>;
return (
<Tippy
interactive
@@ -32,7 +23,7 @@ const Mention = ({ uid, popover = true, cid, textOnly = false }: Props) => {
trigger="click"
content={<Profile uid={uid} type="card" cid={cid} />}
>
<Styled className="clickable">{`@${user.name}`}</Styled>
<span className="px-0.5 text-primary-400 cursor-pointer">{`@${user.name}`}</span>
</Tippy>
);
};
@@ -1,6 +1,5 @@
import { FC } from "react";
import { useEffect } from "react";
import styled from "styled-components";
import toast from "react-hot-toast";
import usePinMessage from "../../hook/usePinMessage";
import StyledModal from "../styled/Modal";
@@ -9,20 +8,6 @@ import Modal from "../Modal";
import PreviewMessage from "./PreviewMessage";
import { useTranslation } from "react-i18next";
const StyledPinModal = styled(StyledModal)`
min-width: 406px;
.title,
.desc {
text-align: left;
}
.preview {
border: 1px solid #f2f4f7;
max-height: 256px;
overflow: auto;
background: none;
overflow-x: hidden;
}
`;
interface Props {
closeModal: () => void;
@@ -40,7 +25,7 @@ const PinMessageModal: FC<Props> = ({ closeModal, mid = 0, gid = 0 }) => {
useEffect(() => {
if (isSuccess) {
closeModal();
toast.success("Pin Message Successfully");
toast.success(t("tip.pin"));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSuccess]);
@@ -48,8 +33,8 @@ const PinMessageModal: FC<Props> = ({ closeModal, mid = 0, gid = 0 }) => {
if (!mid) return null;
return (
<Modal>
<StyledPinModal
// className="animate__animated animate__fadeInDown animate__faster"
<StyledModal
className="min-w-[406px]"
buttons={
<>
<Button onClick={closeModal} className="cancel">
@@ -63,8 +48,8 @@ const PinMessageModal: FC<Props> = ({ closeModal, mid = 0, gid = 0 }) => {
title={t("action.pin")}
description={`Do you want to pin this message to #${channel?.name}`}
>
<PreviewMessage mid={mid} />
</StyledPinModal>
<PreviewMessage mid={mid} context="pin" />
</StyledModal>
</Modal>
);
};
@@ -1,23 +1,26 @@
import { FC } from "react";
import clsx from "clsx";
import dayjs from "dayjs";
import renderContent from "./renderContent";
import Avatar from "../Avatar";
import StyledWrapper from "./styled";
import { useAppSelector } from "../../../app/store";
import { FC } from "react";
interface Props {
mid?: number;
context?: "forward" | "pin"
}
const PreviewMessage: FC<Props> = ({ mid = 0 }) => {
const PreviewMessage: FC<Props> = ({ mid = 0, context }) => {
const { msg, usersData } = useAppSelector((store) => {
return { msg: store.message[mid], usersData: store.users.byId };
});
if (!msg) return null;
const { from_uid = 0, created_at, content_type, content, thumbnail = "", properties } = msg;
const { name, avatar } = usersData[from_uid] ?? {};
const pinMsg = context == "pin";
return (
<StyledWrapper className={`preview`}>
<StyledWrapper className={clsx(`preview`, pinMsg && "max-h-64 !bg-transparent overflow-auto overflow-x-hidden border border-solid border-gray-200 dark:border-gray-400")}>
<div className="avatar">
<Avatar width={40} height={40} src={avatar} name={name} />
</div>
+4 -4
View File
@@ -96,7 +96,7 @@ const Message: FC<IProps> = ({
onContextMenu={readOnly ? undefined : handleContextMenuEvent}
data-msg-mid={mid}
ref={inviewRef}
className={`message ${readOnly ? "readonly" : ""} ${showExpire ? "auto_delete" : ""} ${pinInfo ? "pinned" : ""} ${contextMenuVisible ? "contextVisible" : ""
className={`message dark:hover:!bg-gray-800 ${readOnly ? "readonly" : ""} ${showExpire ? "auto_delete" : ""} ${pinInfo ? "pinned" : ""} ${contextMenuVisible ? "contextVisible" : ""
} `}
>
<Tippy
@@ -133,14 +133,14 @@ const Message: FC<IProps> = ({
placement="top"
tip={dayjsTime.format("YYYY-MM-DD h:mm:ss A")}
>
<i className="time">
<time className="time">
{timePrefix
? `${timePrefix} ${dayjsTime.format("h:mm A")}`
: dayjsTime.format("YYYY-MM-DD h:mm:ss A")}
</i>
</time>
</Tooltip>
</div>
<div className={`down ${sending ? "sending" : ""}`}>
<div className={`down dark:!text-white ${sending ? "sending" : ""}`}>
{reply_mid && <Reply key={reply_mid} mid={reply_mid} />}
{edit ? (
<EditMessage mid={mid} cancelEdit={toggleEditMessage} />
+1 -1
View File
@@ -188,7 +188,7 @@ const Plugins: FC<Props> = ({
return (
<div className="input max-h-[50vh] overflow-auto text-sm text-gray-600" ref={editableRef}>
<div className="input max-h-[50vh] overflow-auto text-sm text-gray-600 dark:text-white" ref={editableRef}>
<Plate
id={`${TEXT_EDITOR_PREFIX}_${id}`}
onChange={handleChange}
+4 -4
View File
@@ -45,8 +45,8 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
const isCard = type == 'card';
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-slate-100 text-sm pt-3.5 pb-3`;
const containerClass = clsx(`flex-center flex-col w-[432px] gap-1 z-[998] mt-20 select-none`, isCard && "p-4 w-[280px] bg-white drop-shadow rounded-md");
const iconClass = `cursor-pointer flex flex-col items-center gap-1 rounded-lg w-32 text-primary-400 bg-slate-100 dark:bg-gray-700 text-sm pt-3.5 pb-3`;
const containerClass = clsx(`flex-center flex-col w-[432px] gap-1 z-[99] mt-20 select-none`, isCard && "p-4 w-[280px] bg-white dark:bg-gray-800 drop-shadow rounded-md");
return (
<div className={containerClass}>
<Avatar
@@ -56,8 +56,8 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
src={avatar}
name={name}
/>
<h2 className="text-lg select-text font-bold text-[#1c1c1e]">{name}</h2>
<span className="text-sm text-[#98a2b3] select-text">{email}</span>
<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")}>
<NavLink to={`/chat/dm/${uid}`}>
+1 -1
View File
@@ -10,7 +10,7 @@ const QRCode = ({ link }: Props) => {
const logo = useAppSelector(store => store.server.logo);
return (
<QRCodeCanvas value={link}
className="rounded border border-solid border-gray-200 p-1 !w-full !h-full"
className="rounded border border-solid border-gray-200 dark:border-gray-100 p-1 !w-full !h-full"
size={512}
bgColor={"#fff"}
fgColor={"#22ccee"}
+3 -3
View File
@@ -13,10 +13,10 @@ const SaveTip: FC<Props> = ({ saveHandler, resetHandler }) => {
const { t } = useTranslation("setting");
// const btnClass=clsx("")
return (
<div className="w-full p-2 absolute bottom-16 left-0 flex items-center justify-between text-gray-500 border border-solid border-gray-200 shadow-md rounded-full">
<span className="p-2 text-sm">{t('save_tip')}</span>
<div className="w-full p-2 absolute bottom-16 left-0 flex items-center justify-between text-gray-500 border border-solid border-gray-200 dark:border-gray-400 dark:bg-gray-600 shadow-md rounded-full">
<span className="p-2 text-sm dark:text-gray-200">{t('save_tip')}</span>
<div className="flex items-center gap-3">
<Button className="small ghost !border-none !text-gray-500 !shadow-none" onClick={resetHandler}>
<Button className="small ghost !border-none !text-gray-500 !shadow-none dark:!text-gray-100" onClick={resetHandler}>
{t('reset')}
</Button>
<Button className="small !rounded-full" onClick={saveHandler}>
@@ -1,52 +1,9 @@
import { ChangeEvent, useState } from "react";
import styled from "styled-components";
import Modal from "../../Modal";
import Button from "../../styled/Button";
import Input from "../../styled/Input";
import CloseIcon from "../../../../assets/icons/close.svg";
const StyledWrapper = styled.div`
background: #fff;
display: flex;
flex-direction: column;
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
border-radius: 12px;
padding: 16px;
width: 406px;
.title {
display: flex;
align-items: center;
justify-content: space-between;
font-style: normal;
font-weight: 600;
font-size: 18px;
line-height: 28px;
color: #344054;
width: 100%;
.close {
cursor: pointer;
}
}
.input {
padding: 16px 0;
display: flex;
flex-direction: column;
gap: 8px;
label {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #475467;
}
}
.btns {
margin-top: 32px;
gap: 16px;
display: flex;
width: 100%;
justify-content: flex-end;
}
`;
export default function EditFileDetails({
name,
@@ -65,23 +22,24 @@ export default function EditFileDetails({
updateName(fileName);
closeModal();
};
return (
<Modal>
<StyledWrapper>
<h4 className="title">
File Details <CloseIcon className="close" onClick={closeModal} />
<div className="bg-white dark:bg-gray-800 flex flex-col drop-shadow rounded-xl p-4 w-[406px]">
<h4 className="flex items-center justify-between font-semibold text-lg text-gray-700 dark:text-gray-100 w-full">
File Details <CloseIcon className="cursor-pointer dark:fill-white" onClick={closeModal} />
</h4>
<div className="input">
<label htmlFor="name">Name</label>
<div className="py-4 flex flex-col gap-2">
<label className="font-semibold text-sm text-gray-600 dark:text-gray-200" htmlFor="name">Name</label>
<Input id="name" value={fileName} onChange={handleNameChange} />
</div>
<div className="btns">
<div className="flex justify-end gap-4 mt-8 w-full">
<Button className="ghost cancel" onClick={closeModal}>
Cancel
</Button>
<Button onClick={handleUpdate}>Save Changes</Button>
</div>
</StyledWrapper>
</div>
</Modal>
);
}
@@ -1,5 +1,4 @@
import { useState, useEffect } from "react";
import Styled from "./styled";
import { useMixedEditor } from "../../MixedInput";
import EditFileDetailsModal from "./EditFileDetails";
import { getFileIcon, formatBytes } from "../../../utils";
@@ -43,6 +42,7 @@ export default function UploadFileList({
}, [stageFiles.length]);
if (!context || !id || !stageFiles || stageFiles.length == 0) return null;
return (
<>
{editInfo && (
@@ -53,21 +53,21 @@ export default function UploadFileList({
/>
)}
<Styled>
<ul className="w-full overflow-auto flex justify-start p-4 pt-6 bg-gray-200 dark:bg-gray-800 rounded-t-lg">
{stageFiles.map(({ name, url, size, type }, idx: number) => {
return (
<li className="file" key={url}>
<div className="preview">
{type.startsWith("image") ? <img src={url} alt="image" /> : getFileIcon(type, name)}
<li className="group relative flex flex-col bg-gray-100 dark:bg-gray-700 rounded p-2" key={url}>
<div className="flex-center w-40 h-40">
{type.startsWith("image") ? <img className="w-full h-full object-cover" src={url} alt="image" /> : getFileIcon(type, name)}
</div>
<h4 className="name">{name}</h4>
<span className="size">{formatBytes(size)}</span>
<ul className="opts">
<li className="opt edit" onClick={handleOpenEditModal.bind(null, idx)}>
<h4 className="w-40 mt-4 mb-0.5 font-semibold text-sm text-gray-800 dark:text-gray-100 whitespace-nowrap overflow-hidden text-ellipsis">{name}</h4>
<span className="text-xs text-gray-500">{formatBytes(size)}</span>
<ul className="invisible group-hover:visible bg-inherit border border-solid border-black/10 box-border rounded-md flex items-center absolute -right-5 -top-2.5">
<li className="p-1 cursor-pointer edit" onClick={handleOpenEditModal.bind(null, idx)}>
<EditIcon />
</li>
<li
className="opt delete"
className="p-1 cursor-pointer delete"
data-index={idx}
onClick={removeStageFile.bind(null, idx)}
>
@@ -77,7 +77,7 @@ export default function UploadFileList({
</li>
);
})}
</Styled>
</ul>
</>
);
}
@@ -1,72 +0,0 @@
import styled from "styled-components";
const Styled = styled.ul`
width: 100%;
overflow: auto;
display: flex;
justify-content: flex-start;
gap: 24px;
padding: 24px 16px 16px 16px;
background: #e5e7eb;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05);
border-radius: 8px 8px 0 0;
.file {
position: relative;
display: flex;
flex-direction: column;
background: #fcfcfd;
border-radius: 4px;
padding: 8px;
.preview {
display: flex;
justify-content: center;
align-items: center;
width: 160px;
height: 160px;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
.name {
width: 160px;
margin: 16px 0 2px 0;
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #1c1c1e;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.size {
font-weight: 400;
font-size: 12px;
line-height: 18px;
color: #616161;
}
.opts {
visibility: hidden;
background: inherit;
border: 1px solid rgba(0, 0, 0, 0.08);
box-sizing: border-box;
border-radius: 6px;
display: flex;
align-items: center;
position: absolute;
right: -20px;
top: -10px;
.opt {
padding: 4px;
cursor: pointer;
}
}
&:hover .opts {
visibility: visible;
}
}
`;
export default Styled;
+1 -1
View File
@@ -137,7 +137,7 @@ const Send: FC<IProps> = ({
context == "channel" ? (channelsData[id]?.is_public ? uids : channelsData[id]?.members) : [];
return (
<StyledSend
className={`send ${mode} ${markdownFullscreen ? "fullscreen" : ""} ${replying_mid ? "reply" : ""
className={`send dark:bg-[#4D5761] ${mode} ${markdownFullscreen ? "fullscreen" : ""} ${replying_mid ? "reply" : ""
} ${context}`}
>
{replying_mid && <Replying context={context} mid={replying_mid} id={id} />}
+2 -2
View File
@@ -28,7 +28,7 @@ export default function Server({ readonly = false }: Props) {
<img alt={`${name} logo`} className="w-full h-full object-cover rounded-full" src={logo} />
</div>
<div className="flex flex-col gap-1">
<h3 className="text-sm text-gray-600" title={description}>
<h3 className="text-sm text-gray-600 dark:text-gray-100" title={description}>
{name}
</h3>
<span className="text-xs text-gray-500">{userCount} {t("members")}</span>
@@ -45,7 +45,7 @@ export default function Server({ readonly = false }: Props) {
<img alt={`${name} logo`} className="w-full h-full object-cover rounded-full" src={logo} />
</div>
<div className="flex flex-col gap-1">
<h3 className="text-sm text-gray-600 font-bold" title={description}>
<h3 className="text-sm text-gray-600 font-bold dark:text-gray-100" title={description}>
{name}
</h3>
<span className="text-xs text-gray-500">{userCount} {t("members")}</span>
+14 -102
View File
@@ -1,96 +1,8 @@
import { FC, PropsWithChildren, ReactNode } from "react";
import styled from "styled-components";
import { useLocation, NavLink } from "react-router-dom";
import backIcon from "../../assets/icons/arrow.left.svg?url";
import IconBack from "../../assets/icons/arrow.left.svg";
import { Nav } from "../../routes/settingChannel/navs";
const StyledWrapper = styled.div`
width: 100vw;
height: 100vh;
display: flex;
> .left {
max-height: 100vh;
overflow: scroll;
padding: 32px 16px;
min-width: 212px;
background-color: #f5f6f7;
> .title {
cursor: pointer;
font-weight: bold;
font-size: 16px;
line-height: 24px;
color: #1c1c1e;
margin-bottom: 32px;
padding-left: 24px;
background-size: 16px;
background: url(${backIcon}) no-repeat left;
}
> .items {
display: flex;
flex-direction: column;
gap: 2px;
margin-bottom: 36px;
&:before {
padding-left: 12px;
content: attr(data-title);
font-weight: bold;
font-size: 12px;
line-height: 18px;
color: #6b7280;
margin-bottom: 2px;
}
.item {
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #44494f;
border-radius: 4px;
&:hover,
&.curr {
background: #e7e5e4;
}
> a {
display: block;
padding: 4px 12px;
}
}
&.danger .item {
cursor: pointer;
padding: 4px 12px;
color: #ef4444;
&:hover {
background: none;
}
}
}
}
> .right {
background-color: #fff;
width: 100%;
max-height: 100%;
overflow: auto;
padding: 32px;
> .title {
font-weight: bold;
font-size: 20px;
line-height: 30px;
color: #374151;
margin-bottom: 32px;
}
}
`;
import clsx from "clsx";
export interface Danger {
title: string;
handler: () => void;
@@ -114,18 +26,18 @@ const StyledSettingContainer: FC<PropsWithChildren<Props>> = ({
}) => {
const { pathname } = useLocation();
return (
<StyledWrapper>
<div className="left">
<h2 onClick={closeModal} className="title">
{title}
<div className="w-screen h-screen flex">
<div className="max-h-screen min-w-[212px] overflow-scroll px-4 py-8 bg-[#f5f6f7] dark:bg-[#1F2A37]">
<h2 onClick={closeModal} className="flex gap-2 items-center cursor-pointer mb-8 font-bold text-gray-800 dark:text-white">
<IconBack className="dark:fill-gray-400" /> {title}
</h2>
{navs.map(({ title, items }) => {
return (
<ul key={title} data-title={title} className="items">
<ul key={title} data-title={title} className="flex flex-col gap-0.5 mb-9 before:pl-3 before:content-[attr(data-title)] before:font-bold before:text-xs before:text-gray-400">
{items.map(({ name, title }) => {
return (
<li key={name} className={`item ${name == nav?.name ? "curr" : ""}`}>
<NavLink to={`${pathname}?nav=${name}`}>{title}</NavLink>
<li key={name} className={clsx(`text-sm text-gray-700 whitespace-nowrap dark:text-gray-200 rounded hover:bg-[#e7e5e4] dark:hover:bg-slate-500/20`, name == nav?.name && "bg-[#e7e5e4] dark:bg-slate-500/20")}>
<NavLink to={`${pathname}?nav=${name}`} className="block px-3 py-1">{title}</NavLink>
</li>
);
})}
@@ -133,12 +45,12 @@ const StyledSettingContainer: FC<PropsWithChildren<Props>> = ({
);
})}
{dangers.length ? (
<ul className="items danger">
<ul className="flex flex-col gap-2 mb-9">
{dangers.map((d) => {
if (typeof d === "boolean" || !d) return null;
const { title, handler } = d;
return (
<li key={title} onClick={handler} className="item">
<li key={title} onClick={handler} className="text-sm text-white dark:text-gray-200 rounded cursor-pointer py-1.5 px-3 bg-[#ef4444] hover:bg-red-600">
{title}
</li>
);
@@ -146,11 +58,11 @@ const StyledSettingContainer: FC<PropsWithChildren<Props>> = ({
</ul>
) : null}
</div>
<div className="right">
{nav && <h4 className="title">{nav.title}</h4>}
<div className="bg-white w-full max-h-full overflow-auto p-8 dark:bg-[#384250]">
{nav && <h4 className="font-bold text-xl text-gray-600 mb-8 dark:text-gray-100">{nav.title}</h4>}
{children}
</div>
</StyledWrapper>
</div>
);
};
+1 -1
View File
@@ -27,7 +27,7 @@ const Tooltip: FC<Props> = ({ tip = "", placement = "right", delay = null, child
duration={delay ? defaultDuration : 0}
delay={delay ?? [150, 0]}
placement={placement}
content={<div className="relative bg-white px-3 py-2 text-xs rounded-lg drop-shadow text-gray-700">
content={<div className="relative bg-white dark:bg-gray-800 px-3 py-2 text-xs rounded-lg drop-shadow text-gray-700 dark:text-gray-100">
<Triangle placement={placement} />
{tip}
</div>}
+1 -1
View File
@@ -44,7 +44,7 @@ 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 p-2 rounded-lg select-none `, interactive && "hover:bg-gray-500/10", compact && "p-0");
const nameClass = clsx(`text-sm text-gray-500 max-w-[190px] overflow-hidden text-ellipsis font-semibold`);
const nameClass = clsx(`text-sm text-gray-500 max-w-[190px] overflow-hidden text-ellipsis 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`,
online ? "bg-[#22c55e]" : "bg-[#a1a1aa]",
compact && "w-3.5 h-3.5");
+1 -1
View File
@@ -6,7 +6,7 @@ const Version: FC<Props> = () => {
const { t } = useTranslation("setting", { keyPrefix: "version" });
const { data: serverVersion } = useGetServerVersionQuery();
return (
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-3 dark:text-white">
<div className="item">{t("client_version")}: {process.env.VERSION}</div>
<div className="item">{t("server_version")}: {serverVersion}</div>
<div className="item">{t("build_time")}: {process.env.REACT_APP_BUILD_TIME}</div>
+3 -3
View File
@@ -12,9 +12,9 @@ interface Props {
const StyledModal: FC<Props> = ({ compact = false, title = "", description = "", buttons, children, className }) => {
return (
<div className={clsx("rounded-lg bg-white drop-shadow", compact ? "p-4 min-w-[406px] text-left" : "p-8 min-w-[440px] text-center", className)} >
{title && <h3 className="text-xl text-gray-600 mb-4 font-semibold">{title}</h3>}
{description && <p className="text-sm text-gray-400 mb-2">{description}</p>}
<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)} >
{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}
{buttons && <div className="pt-4 w-full flex justify-end gap-4 items-center">{buttons}</div>}
</div>