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
@@ -1,4 +1,4 @@
<svg width="6" height="6" viewBox="0 0 6 6" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="6" height="6" viewBox="0 0 6 6" fill="black" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.01" width="6" height="6" fill="#D8D8D8"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.62506 1.64911C5.41795 1.4503 5.08216 1.45029 4.87506 1.64911L3.00003 3.44911L1.12506 1.64916C0.917951 1.45034 0.582164 1.45034 0.375057 1.64916C0.16795 1.84798 0.16795 2.17033 0.375057 2.36915L2.62506 4.52912C2.83217 4.72794 3.16795 4.72794 3.37506 4.52912C3.37665 4.5276 3.37822 4.52608 3.37977 4.52454L5.62506 2.36911C5.83216 2.17029 5.83216 1.84793 5.62506 1.64911Z" fill="black" fill-opacity="0.5"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.62506 1.64911C5.41795 1.4503 5.08216 1.45029 4.87506 1.64911L3.00003 3.44911L1.12506 1.64916C0.917951 1.45034 0.582164 1.45034 0.375057 1.64916C0.16795 1.84798 0.16795 2.17033 0.375057 2.36915L2.62506 4.52912C2.83217 4.72794 3.16795 4.72794 3.37506 4.52912C3.37665 4.5276 3.37822 4.52608 3.37977 4.52454L5.62506 2.36911C5.83216 2.17029 5.83216 1.84793 5.62506 1.64911Z" fill-opacity="0.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 614 B

After

Width:  |  Height:  |  Size: 603 B

+2 -2
View File
@@ -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="M3.5 2C2.67157 2 2 2.67157 2 3.5V5.5C2 6.32843 2.67157 7 3.5 7H5.5C6.32843 7 7 6.32843 7 5.5V3.5C7 2.67157 6.32843 2 5.5 2H3.5ZM10.5 2C9.67157 2 9 2.67157 9 3.5V5.5C9 6.32843 9.67157 7 10.5 7H12.5C13.3284 7 14 6.32843 14 5.5V3.5C14 2.67157 13.3284 2 12.5 2H10.5ZM3.5 9C2.67157 9 2 9.67157 2 10.5V12.5C2 13.3284 2.67157 14 3.5 14H5.5C6.32843 14 7 13.3284 7 12.5V10.5C7 9.67157 6.32843 9 5.5 9H3.5ZM10.5 9C9.67157 9 9 9.67157 9 10.5V12.5C9 13.3284 9.67157 14 10.5 14H12.5C13.3284 14 14 13.3284 14 12.5V10.5C14 9.67157 13.3284 9 12.5 9H10.5Z" fill="#344054"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="#344054" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 2C2.67157 2 2 2.67157 2 3.5V5.5C2 6.32843 2.67157 7 3.5 7H5.5C6.32843 7 7 6.32843 7 5.5V3.5C7 2.67157 6.32843 2 5.5 2H3.5ZM10.5 2C9.67157 2 9 2.67157 9 3.5V5.5C9 6.32843 9.67157 7 10.5 7H12.5C13.3284 7 14 6.32843 14 5.5V3.5C14 2.67157 13.3284 2 12.5 2H10.5ZM3.5 9C2.67157 9 2 9.67157 2 10.5V12.5C2 13.3284 2.67157 14 3.5 14H5.5C6.32843 14 7 13.3284 7 12.5V10.5C7 9.67157 6.32843 9 5.5 9H3.5ZM10.5 9C9.67157 9 9 9.67157 9 10.5V12.5C9 13.3284 9.67157 14 10.5 14H12.5C13.3284 14 14 13.3284 14 12.5V10.5C14 9.67157 13.3284 9 12.5 9H10.5Z" />
</svg>

Before

Width:  |  Height:  |  Size: 669 B

After

Width:  |  Height:  |  Size: 658 B

+2 -2
View File
@@ -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="M2 3.75C2 3.33579 2.33579 3 2.75 3H10.25C10.6642 3 11 3.33579 11 3.75C11 4.16421 10.6642 4.5 10.25 4.5H2.75C2.33579 4.5 2 4.16421 2 3.75ZM2 11.75C2 11.3358 2.33579 11 2.75 11H9.25C9.66421 11 10 11.3358 10 11.75C10 12.1642 9.66421 12.5 9.25 12.5H2.75C2.33579 12.5 2 12.1642 2 11.75ZM2.75 7C2.33579 7 2 7.33579 2 7.75C2 8.16421 2.33579 8.5 2.75 8.5H13.25C13.6642 8.5 14 8.16421 14 7.75C14 7.33579 13.6642 7 13.25 7H2.75Z" fill="#344054"/>
<svg width="16" height="16" viewBox="0 0 16 16" fill="#344054" xmlns="http://www.w3.org/2000/svg">
<path d="M2 3.75C2 3.33579 2.33579 3 2.75 3H10.25C10.6642 3 11 3.33579 11 3.75C11 4.16421 10.6642 4.5 10.25 4.5H2.75C2.33579 4.5 2 4.16421 2 3.75ZM2 11.75C2 11.3358 2.33579 11 2.75 11H9.25C9.66421 11 10 11.3358 10 11.75C10 12.1642 9.66421 12.5 9.25 12.5H2.75C2.33579 12.5 2 12.1642 2 11.75ZM2.75 7C2.33579 7 2 7.33579 2 7.75C2 8.16421 2.33579 8.5 2.75 8.5H13.25C13.6642 8.5 14 8.16421 14 7.75C14 7.33579 13.6642 7 13.25 7H2.75Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 549 B

After

Width:  |  Height:  |  Size: 537 B

+2 -2
View File
@@ -1,3 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 6H21M8 12H21M8 18H21M3 6H3.01M3 12H3.01M3 18H3.01" stroke="#101828" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<svg width="24" height="24" viewBox="0 0 24 24" stroke="#101828" xmlns="http://www.w3.org/2000/svg">
<path d="M8 6H21M8 12H21M8 18H21M3 6H3.01M3 12H3.01M3 18H3.01" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 249 B

After

Width:  |  Height:  |  Size: 238 B

+40
View File
@@ -26,6 +26,43 @@
.checkbox:checked::before {
transform: scale(1);
}
/* markdown editor */
.md-editor {
position: relative;
width: 100%;
width: -webkit-fill-available;
margin-top: 16px;
}
.md-editor > div {
transition: height 0.5s ease 0s;
}
.md-editor .toastui-editor-defaultUI {
border-bottom: none;
border-radius: 0;
border-top: 1px solid #d0d5dd;
border-left: none;
border-right: none;
}
.md-editor .toastui-editor {
padding: 16px 0;
}
.md-editor .toastui-editor [contenteditable="true"] {
padding: 0;
}
.md-editor .toastui-editor-md-preview {
padding-top: 16px;
}
.md-editor .toastui-editor-md-preview .toastui-editor-contents {
padding: 0;
}
.md-editor .toastui-editor-toolbar {
display: none;
}
.md-editor .send {
position: absolute;
bottom: 15px;
right: 15px;
}
}
/* 全局设置滚动条 */
::-webkit-scrollbar-thumb {
@@ -37,6 +74,9 @@
width: 5px;
height: 5px;
}
html.dark ::-webkit-scrollbar-thumb {
background: #1c1c1e;
}
:root {
/* border radius */
+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>
+4 -4
View File
@@ -145,7 +145,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
<header className="head flex items-center h-full justify-center md:justify-between">
<div className="flex items-center gap-1 text-base">
<ChannelIcon personal={!is_public} />
<span className="text-gray-800">{name}</span>
<span className="text-gray-800 dark:text-white">{name}</span>
<span className="ml-2 text-gray-500">{description}</span>
</div>
</header>
@@ -155,7 +155,7 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
{addVisible && (
<div className="add" onClick={toggleAddVisible}>
<img className="icon" src={addIcon} />
<div className="txt">{t("add_channel_members")}</div>
<div className="txt dark:!text-gray-50">{t("add_channel_members")}</div>
</div>
)}
{memberIds.map((uid: number) => {
@@ -179,8 +179,8 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
<LoadMore pullUp={pullUp} pulling={pulling} />
) : (
<div className="info">
<h2 className="title">{t("welcome_channel", { name })}</h2>
<p className="desc">{t("welcome_desc", { name })} </p>
<h2 className="title dark:text-white">{t("welcome_channel", { name })}</h2>
<p className="desc dark:!text-gray-300">{t("welcome_desc", { name })} </p>
{loginUser?.is_admin && (
<NavLink to={`/setting/channel/${cid}?f=${pathname}`} className="edit">
<EditIcon className="icon" />
-25
View File
@@ -1,30 +1,5 @@
import styled from "styled-components";
export const StyledNotification = styled.div`
padding: 3px 8px;
font-style: normal;
font-weight: 600;
font-size: 12px;
line-height: 18px;
color: #fff;
position: absolute;
top: 0;
left: 10px;
width: 900px;
height: 24px;
background: linear-gradient(135deg, #3c8ce7 0%, #00eaff 100%);
border-radius: 0px 0px 8px 8px;
display: flex;
align-items: center;
justify-content: space-between;
.clear {
cursor: pointer;
color: inherit;
border: none;
background: none;
outline: none;
}
`;
export const StyledUsers = styled.div`
/* display: flex; */
flex-direction: column;
+3 -3
View File
@@ -20,10 +20,10 @@ const GuestBlankPlaceholder = () => {
navigateTo("/login");
};
return (
<section className="flex flex-col items-center">
<h2 className="text-3xl text-gray-600 font-bold ">{t("welcome", { name: serverName })}</h2>
<section className="flex flex-col items-center bg-transparent">
<h2 className="text-3xl text-gray-600 dark:text-gray-50 font-bold ">{t("welcome", { name: serverName })}</h2>
<div className="flex flex-col">
<span className="text-gray-400 my-3 text-sm">{t("guest_login_tip")}</span>
<span className="text-gray-400 dark:text-gray-200 my-3 text-sm">{t("guest_login_tip")}</span>
<Button onClick={handleSignIn} className="small">{t("sign_in")}</Button>
</div>
</section>
+8 -8
View File
@@ -1,13 +1,13 @@
// import { useState, useEffect } from "react";
// import { NavLink } from "react-router-dom";
import { useTranslation } from "react-i18next";
import useMessageFeed from "../../../common/hook/useMessageFeed";
import ChannelIcon from "../../../common/component/ChannelIcon";
import Layout from "../Layout";
import { renderMessageFragment } from "../utils";
import { StyledChannelChat } from "./styled";
import LoadMore from "../LoadMore";
import { useAppSelector } from "../../../app/store";
import { useTranslation } from "react-i18next";
type Props = {
cid?: number;
};
@@ -43,19 +43,19 @@ export default function GuestChannelChat({ cid = 0 }: Props) {
<header className="head flex items-center h-full justify-center md:justify-between">
<div className="flex items-center gap-1 text-base">
<ChannelIcon personal={!is_public} />
<span className="text-gray-800">{name}</span>
<span className="text-gray-800 dark:text-white">{name}</span>
<span className="ml-2 text-gray-500">{description}</span>
</div>
</header>
}
>
<StyledChannelChat id={`VOCECHAT_FEED_channel_${cid}`}>
<article className="py-4.5 px-4 w-full h-full overflow-x-hidden overflow-y-auto" id={`VOCECHAT_FEED_channel_${cid}`}>
{hasMore ? (
<LoadMore pullUp={pullUp} pulling={pulling} />
) : (
<div className="info">
<h2 className="title">{t("welcome_channel", { name })}</h2>
<p className="desc">{t("welcome_desc", { name })} </p>
<div className="pt-14 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>
</div>
)}
{/* <div className="feed"> */}
@@ -73,7 +73,7 @@ export default function GuestChannelChat({ cid = 0 }: Props) {
context: "channel"
});
})}
</StyledChannelChat>
</article>
</Layout>
</>
);
@@ -1,28 +0,0 @@
import styled from "styled-components";
export const StyledChannelChat = styled.article`
padding: 18px 16px;
width: 100%;
height: 100%;
height: -webkit-fill-available;
overflow-x: hidden;
overflow-y: auto;
overflow-anchor: auto;
> .info {
padding-top: 62px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
.title {
font-weight: bold;
font-size: 36px;
line-height: 44px;
}
.desc {
color: #78787c;
font-weight: 500;
font-size: 16px;
line-height: 24px;
}
}
`;
+11 -11
View File
@@ -1,9 +1,9 @@
// @ts-nocheck
import { useState, useEffect, FC } from "react";
import dayjs from "dayjs";
import clsx from "clsx";
import { renderPreviewMessage } from "../../chat/utils";
import Avatar from "../../../common/component/Avatar";
import IconLock from "../../../assets/icons/lock.svg";
import { NavLink } from "react-router-dom";
import { useAppSelector } from "../../../app/store";
@@ -35,25 +35,25 @@ const Session: FC<IProps> = ({ id, mid }) => {
}, [id, mid, userData, channelData]);
if (!data) return null;
const previewMsg = messageData[mid] || {};
const { name, icon, is_public } = data;
const { name, icon } = data;
return (
<li className="session">
<NavLink className={`nav`} to={`/chat/channel/${id}`}>
<div className="icon">
<NavLink className={({ isActive: linkActive }) => clsx(`nav flex gap-2 rounded-lg p-2 w-full hover:bg-gray-500/20`, linkActive && "bg-gray-500/20")} to={`/chat/channel/${id}`}>
<div className="flex bg-slate-50 rounded-full">
<Avatar width={40} height={40} className="icon" type="channel" name={name} src={icon} />
</div>
<div className="details">
<div className="up">
<span className={`name ${previewMsg.created_at ? "" : "only_title"}`}>
{name} {!is_public && <IconLock />}
<div className="w-full flex flex-col justify-between">
<div className="flex items-center justify-between">
<span className={clsx(`flex items-center gap-2 font-semibold text-sm text-gray-500 dark:text-white whitespace-nowrap overflow-hidden text-ellipsis`, previewMsg.created_at && "max-w-[190px]")}>
{name}
</span>
<span className="time">
<span className="text-xs text-gray-600 whitespace-nowrap overflow-hidden max-w-[80px] text-ellipsis">
{previewMsg.created_at ? dayjs(previewMsg.created_at).fromNow() : null}
</span>
</div>
<div className="down">
<span className="msg">{renderPreviewMessage(previewMsg)}</span>
<div className="flex items-center justify-between">
<span className="text-xs text-gray-500 whitespace-nowrap overflow-hidden w-36 text-ellipsis">{renderPreviewMessage(previewMsg)}</span>
</div>
</div>
</NavLink>
+2 -3
View File
@@ -1,6 +1,5 @@
import { FC } from "react";
import { useState, useEffect } from "react";
import Styled from "./styled";
import Session from "./Session";
import { useAppSelector } from "../../../app/store";
export interface ChatSession {
@@ -41,12 +40,12 @@ const SessionList: FC<Props> = () => {
}, [channelIDs, channelMessage, readChannels, readUsers, loginUid, userMessage]);
return (
<Styled>
<ul className="flex flex-col gap-0.5 p-2 overflow-auto h-[calc(100vh_-_56px_-_16px_-_8px)]">
{sessions.map((s) => {
const { key, id, mid = 0 } = s;
return <Session key={key} id={id} mid={mid} />;
})}
</Styled>
</ul>
);
};
export default SessionList;
@@ -1,86 +0,0 @@
import styled from "styled-components";
const Styled = styled.ul`
display: flex;
flex-direction: column;
gap: 2px;
padding: 8px;
height: calc(100vh - 56px - 16px - 8px);
overflow: auto;
> .session {
> a {
display: flex;
gap: 8px;
border-radius: 8px;
padding: 8px;
width: 100%;
&.active,
&:hover {
background: rgba(116, 127, 141, 0.2);
}
.icon {
display: flex;
background-color: #eee;
border-radius: 50%;
img {
max-width: unset;
width: 40px;
height: 40px;
&.channel_default {
padding: 5px;
}
}
}
.details {
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
.up {
display: flex;
align-items: center;
justify-content: space-between;
.name {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #52525b;
max-width: 112px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.only_title {
max-width: 190px;
}
}
.time {
font-weight: 500;
font-size: 12px;
line-height: 18px;
color: #78787c;
white-space: nowrap;
overflow: hidden;
max-width: 80px;
text-overflow: ellipsis;
}
}
.down {
display: flex;
align-items: center;
justify-content: space-between;
> .msg {
font-weight: 400;
font-size: 12px;
line-height: 18px;
color: #78787c;
white-space: nowrap;
overflow: hidden;
width: 140px;
text-overflow: ellipsis;
}
}
}
}
}
`;
export default Styled;
+2 -2
View File
@@ -20,8 +20,8 @@ const LoginTip = () => {
};
return (
<div className="flex items-center justify-between bg-slate-200/80 rounded-lg w-full p-4">
<span className="text-base text-[#98a2b3]">
<div className="flex items-center justify-between bg-slate-200/80 dark:bg-gray-800 rounded-lg w-full p-4">
<span className="text-base text-[#98a2b3] dark:text-gray-100">
<i className="mr-2 text-lg">👋</i>
{t("sign_in_tip")}
</span>
-2
View File
@@ -2,7 +2,6 @@ import styled from "styled-components";
const Styled = styled.article`
position: relative;
width: 100%;
background: #fff;
border-top-right-radius: 16px;
border-bottom-right-radius: 16px;
> .head {
@@ -21,7 +20,6 @@ const Styled = styled.article`
> .chat {
border-bottom-right-radius: 16px;
width: 100%;
background-color: #fff;
display: flex;
flex-direction: column;
padding: 0;
+12 -12
View File
@@ -1,15 +1,16 @@
// @ts-nocheck
import { useState, useEffect, FC } from "react";
import clsx from "clsx";
import dayjs from "dayjs";
import { useDrop } from "react-dnd";
import { NativeTypes } from "react-dnd-html5-backend";
import { useNavigate, NavLink } from "react-router-dom";
import ContextMenu from "./ContextMenu";
import getUnreadCount, { renderPreviewMessage } from "../utils";
import User from "../../../common/component/User";
import Avatar from "../../../common/component/Avatar";
import IconLock from "../../../assets/icons/lock.svg";
import useContextMenu from "../../../common/hook/useContextMenu";
import { useNavigate, NavLink } from "react-router-dom";
import useUploadFile from "../../../common/hook/useUploadFile";
import { useAppSelector } from "../../../app/store";
@@ -95,7 +96,6 @@ const Session: FC<IProps> = ({
messageData,
loginUid
});
return (
<li className="session">
<ContextMenu
@@ -109,11 +109,11 @@ const Session: FC<IProps> = ({
>
<NavLink
ref={drop}
className={`nav ${isActive ? "drop_over" : ""} ${muted ? "muted" : ""}`}
className={({ isActive: linkActive }) => clsx(`nav flex gap-2 rounded-lg p-2 w-full hover:bg-gray-500/20`, isActive && "shadow-[inset_0_0_0_2px_#52edff]", linkActive && "bg-gray-500/20")}
to={type == "user" ? `/chat/dm/${id}` : `/chat/channel/${id}`}
onContextMenu={handleContextMenuEvent}
>
<div className="icon">
<div className="flex bg-slate-50 rounded-full">
{type == "user" ? (
<User avatarSize={40} compact interactive={false} uid={id} />
) : (
@@ -127,21 +127,21 @@ const Session: FC<IProps> = ({
/>
)}
</div>
<div className="details">
<div className="up">
<span className={`name ${previewMsg.created_at ? "" : "only_title"}`}>
<div className="w-full flex flex-col justify-between">
<div className="flex items-center justify-between">
<span className={clsx(`flex items-center gap-2 font-semibold text-sm text-gray-500 dark:text-white whitespace-nowrap overflow-hidden text-ellipsis`, previewMsg.created_at && "max-w-[190px]")}>
{name} {!is_public && <IconLock />}
</span>
<span className="time">
<span className="text-xs text-gray-600 whitespace-nowrap overflow-hidden max-w-[80px] text-ellipsis">
{previewMsg.created_at ? dayjs(previewMsg.created_at).fromNow() : null}
</span>
</div>
<div className="down">
<span className="msg">{renderPreviewMessage(previewMsg)}</span>
<div className="flex items-center justify-between">
<span className="text-xs text-gray-500 whitespace-nowrap overflow-hidden w-36 text-ellipsis">{renderPreviewMessage(previewMsg)}</span>
{unreads > 0 && (
<i className={`badge ${unreads > 99 ? "dot" : ""}`}>
<strong className={clsx(`text-white h-5 min-w-[20px] bg-primary-400 font-bold text-[10px] rounded-[10px] flex-center`, unreads > 99 && 'w-1.5 h-1.5 p-0 min-w-[unset]', muted && "bg-gray-500")}>
{unreads > 99 ? null : unreads}
</i>
</strong>
)}
</div>
</div>
+2 -4
View File
@@ -1,6 +1,5 @@
import { FC } from "react";
import { useState, useEffect } from "react";
import Styled from "./styled";
import Session from "./Session";
import DeleteChannelConfirmModal from "../../settingChannel/DeleteConfirmModal";
import InviteModal from "../../../common/component/InviteModal";
@@ -65,10 +64,9 @@ const SessionList: FC<Props> = ({ tempSession }) => {
userMessage,
tempSession
]);
return (
<>
<Styled>
<ul className="flex flex-col gap-0.5 p-2 overflow-auto h-[calc(100vh_-_56px_-_16px_-_8px)]">
{sessions.map((s) => {
const { key, type, id, mid = 0 } = s;
return (
@@ -82,7 +80,7 @@ const SessionList: FC<Props> = ({ tempSession }) => {
/>
);
})}
</Styled>
</ul>
{!!deleteId && (
<DeleteChannelConfirmModal
id={deleteId}
-124
View File
@@ -1,124 +0,0 @@
import styled from "styled-components";
const Styled = styled.ul`
display: flex;
flex-direction: column;
gap: 2px;
padding: 8px;
height: calc(100vh - 56px - 16px - 8px);
overflow: auto;
> .session {
> a {
display: flex;
gap: 8px;
border-radius: 8px;
padding: 8px;
width: 100%;
&.active,
&:hover {
background: rgba(116, 127, 141, 0.2);
}
&.drop_over {
box-shadow: inset 0 0 0 2px #52edff;
}
.icon {
display: flex;
background-color: #eee;
border-radius: 50%;
img {
max-width: unset;
width: 40px;
height: 40px;
&.channel_default {
padding: 5px;
}
}
}
.details {
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
.up {
display: flex;
align-items: center;
justify-content: space-between;
.name {
display: flex;
align-items: center;
gap: 2px;
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #52525b;
max-width: 112px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.only_title {
max-width: 190px;
}
}
.time {
font-weight: 500;
font-size: 12px;
line-height: 18px;
color: #78787c;
white-space: nowrap;
overflow: hidden;
max-width: 80px;
text-overflow: ellipsis;
}
}
.down {
display: flex;
align-items: center;
justify-content: space-between;
> .msg {
font-weight: 400;
font-size: 12px;
line-height: 18px;
color: #78787c;
white-space: nowrap;
overflow: hidden;
width: 140px;
text-overflow: ellipsis;
}
> .badge {
color: #fff;
height: 20px;
min-width: 20px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background: #1fe1f9;
font-weight: 900;
font-size: 10px;
line-height: 10px;
&.dot {
min-width: unset;
width: 6px;
height: 6px;
padding: 0;
}
&.mute {
background: #bfbfbf;
}
}
}
}
&.muted {
.up .name,
.up .time,
.down .msg {
color: #d0d5dd;
}
.down .badge {
background: #bfbfbf;
}
}
}
}
`;
export default Styled;
+5 -5
View File
@@ -14,7 +14,7 @@ import GuestBlankPlaceholder from "./GuestBlankPlaceholder";
import GuestChannelChat from "./GuestChannelChat";
import GuestSessionList from "./GuestSessionList";
import IconList from '../../assets/icons/list.svg';
import IconClose from '../../assets/icons/close.svg';
// import IconClose from '../../assets/icons/close.svg';
function ChatPage() {
const [sessionListVisible, setSessionListVisible] = useState(false);
const [channelModalVisible, setChannelModalVisible] = useState(false);
@@ -52,16 +52,16 @@ function ChatPage() {
<ChannelModal closeModal={toggleChannelModalVisible} personal={true} />
)}
{usersModalVisible && <UsersModal closeModal={toggleUsersModalVisible} />}
<StyledWrapper className={`${isGuest ? "!pr-1 !pt-0" : ""} md:!pr-12`}>
<StyledWrapper className={`${isGuest ? "!pr-1 !pt-0" : ""} 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 !fixed top-0 left-0 z-40 transition-all md:!relative md:translate-x-0 md:overflow-auto", sessionListVisible ? "translate-x-0" : "-translate-x-full")}>
<div className={clsx("left fixed md:relative top-0 left-0 z-40 transition-all md:overflow-auto bg-white dark:!bg-[#1F2A37]", sessionListVisible ? "max-md:translate-x-0" : "max-md:-translate-x-full")}>
<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 />
<IconList className="dark:stroke-gray-300" />
</button>}
</div>
<div className={`right ${placeholderVisible ? "placeholder" : ""}`}>
<div className={`right ${placeholderVisible ? "placeholder" : ""} bg-white dark:!bg-[#384250]`}>
{placeholderVisible && (isGuest ? <GuestBlankPlaceholder /> : <BlankPlaceholder />)}
{channel_id !== 0 &&
(isGuest ? (
-5
View File
@@ -6,14 +6,10 @@ const StyledWrapper = styled.div`
padding-bottom: 10px;
padding-right: 4px;
> .left {
background-color: #fff;
position: relative;
/* display: flex; */
flex-direction: column;
min-width: 268px;
box-shadow: inset -1px 0px 0px rgba(0, 0, 0, 0.05);
height: 100%;
/* overflow: auto; */
border-radius: 16px 0 0 16px;
.list {
margin: 12px 8px;
@@ -149,7 +145,6 @@ const StyledWrapper = styled.div`
border-radius: 0 16px 16px 0;
width: 100%;
&.placeholder {
background-color: #fff;
height: 100%;
display: flex;
align-items: center;
+2 -2
View File
@@ -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">
<div className="h-screen flex bg-white mt-2 mr-6 mb-2.5 overflow-auto dark:bg-[#384250] rounded-2xl">
<div className="min-w-[268px] p-2 shadow-inner-[-1px_0px_0px_rgba(0,_0,_0,_0.1)]">
<ul className="flex flex-col gap-0.5">
{Filters.map(({ icon, title, filter: f }) => {
@@ -129,7 +129,7 @@ function FavsPage() {
onClick={handleFilter.bind(null, f as filter)}
>
{icon}
<span className="font-bold text-sm text-gray-600">{title}</span>
<span className="font-bold text-sm text-gray-600 dark:text-gray-100">{title}</span>
</li>
);
})}
+3 -20
View File
@@ -2,36 +2,19 @@ import { FC } from "react";
import { NavLink, useLocation } from "react-router-dom";
import Tooltip from "../../common/component/Tooltip";
import settingIcon from "../../assets/icons/setting.svg?url";
import styled from "styled-components";
const StyledMenus = styled.ul`
display: flex;
flex-direction: column;
position: absolute;
left: 0;
bottom: 0;
width: 100%;
padding: 8px 12px;
.menu {
cursor: pointer;
display: flex;
align-items: center;
padding: 10px;
gap: 10px;
}
`;
type Props = {};
const Menu: FC<Props> = () => {
const { pathname } = useLocation();
return (
<StyledMenus>
<li className="menu link_navs">
<ul className="flex flex-col absolute left-0 bottom-0 w-full px-3 py-2">
<li className="cursor-pointer flex items-center p-2.5 gap-2.5 link_navs">
<NavLink className="link" to={`/setting?f=${pathname}`}>
<Tooltip placement="right" tip="Settings">
<img src={settingIcon} alt="setting icon" className="w-6 h-6 max-w-[unset]" />
</Tooltip>
</NavLink>
</li>
</StyledMenus>
</ul>
);
};
export default Menu;
+1 -1
View File
@@ -57,7 +57,7 @@ function HomePage() {
<>
<Manifest />
{!guest && <Notification />}
<StyledWrapper className={guest ? "guest" : ""}>
<StyledWrapper className={`dark:!bg-[#121926] ${guest ? "guest" : ""}`}>
{!guest && (
<div className={`col left`}>
{loginUid && <User uid={loginUid} />}
+20 -15
View File
@@ -1,11 +1,12 @@
import { useState, useEffect, FormEvent, ChangeEvent, FC } from "react";
import StyledWrapper from "./styled";
import { Navigate } from "react-router-dom";
import toast from "react-hot-toast";
import BASE_URL from "../../app/config";
import { useRegisterMutation } from "../../app/services/auth";
import { useCheckMagicTokenValidMutation } from "../../app/services/auth";
import { useAppSelector } from "../../app/store";
import StyledButton from "../../common/component/styled/Button";
import Input from "../../common/component/styled/Input";
interface AuthForm {
name: string;
@@ -117,15 +118,16 @@ const InvitePage: FC = () => {
if (!valid) return <>invite token expires or invalid</>;
return (
<StyledWrapper>
<div className="form animate__animated animate__fadeInDown animate__faster">
<div className="tips">
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
<h2 className="title">Sign Up to VoceChat</h2>
<span className="desc">Please enter your details.</span>
<div className="flex-center h-screen dark:bg-[#384250]">
<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" />
<h2 className="font-semibold text-2xl text-gray-800 dark:text-white mb-2">Sign Up to VoceChat</h2>
<span className="text-gray-400 dark:text-gray-100">Please enter your details.</span>
</div>
<form onSubmit={handleReg}>
<input
<form className="flex flex-col gap-5 min-w-[360px]" onSubmit={handleReg}>
<Input
className="large"
name="name"
value={name}
required
@@ -133,7 +135,8 @@ const InvitePage: FC = () => {
data-type="name"
onChange={handleInput}
/>
<input
<Input
className="large"
name="email"
value={email}
required
@@ -141,7 +144,8 @@ const InvitePage: FC = () => {
data-type="email"
onChange={handleInput}
/>
<input
<Input
className="large"
type="password"
value={password}
name="password"
@@ -150,7 +154,8 @@ const InvitePage: FC = () => {
onChange={handleInput}
placeholder="Enter your password"
/>
<input
<Input
className="large"
type="password"
value={secondPwd}
name="password"
@@ -160,12 +165,12 @@ const InvitePage: FC = () => {
onChange={handleSecondPwdInput}
placeholder="Enter your password again"
/>
<button disabled={isLoading || isSuccess} className="btn" type="submit">
<StyledButton disabled={isLoading || isSuccess} className="flex justify-center" type="submit">
Sign Up
</button>
</StyledButton>
</form>
</div>
</StyledWrapper>
</div>
);
};
-74
View File
@@ -1,74 +0,0 @@
import styled from "styled-components";
const StyledWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
.form {
padding: 36px 40px 32px 40px;
box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
border-radius: 12px;
.tips {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 24px;
.logo {
width: 56px;
height: 56px;
margin-bottom: 28px;
}
.title {
font-weight: 600;
font-size: 24px;
line-height: 32px;
color: #101828;
margin-bottom: 8px;
}
}
.desc {
font-weight: normal;
font-size: 16px;
line-height: 24px;
color: #667085;
}
form {
display: flex;
flex-direction: column;
gap: 20px;
input {
width: 360px;
background: #ffffff;
border: 1px solid #d0d5dd;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
padding: 10px 14px;
font-weight: normal;
font-size: 16px;
line-height: 24px;
color: #667085;
}
}
.btn {
display: inline-block;
text-align: center;
width: 100%;
font-weight: 500;
font-size: 16px;
line-height: 24px;
color: #ffffff;
padding: 10px;
background: #1fe1f9;
border: 1px solid #1fe1f9;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
&.google {
color: #344054;
border-color: #d0d5dd;
background: none;
}
}
}
`;
export default StyledWrapper;
+1 -1
View File
@@ -8,5 +8,5 @@ export default function MagicLinkLogin() {
// signIn();
};
return <Button className="w-full mb-4" onClick={handleGoogleLogin}>Sign in with Magic Link</Button>;
return <Button className="w-full" onClick={handleGoogleLogin}>Sign in with Magic Link</Button>;
}
+5 -6
View File
@@ -3,11 +3,10 @@ import { useState, useEffect, useRef } from "react";
import MetaMaskOnboarding from "@metamask/onboarding";
import { useLazyGetMetamaskNonceQuery } from "../../app/services/auth";
import metamaskSvg from "../../assets/icons/metamask.svg?url";
import { StyledSocialButton } from "./styled";
import Onboarding from "@metamask/onboarding";
import { LoginCredential } from "../../types/auth";
import { useTranslation } from "react-i18next";
import { AuthType } from "../../types/common";
import Button from "../../common/component/styled/Button";
// import toast from "react-hot-toast";
export default function MetamaskLoginButton({
@@ -21,7 +20,7 @@ export default function MetamaskLoginButton({
const [requesting, setRequesting] = useState(false);
const [accounts, setAccounts] = useState([]);
const [getNonce] = useLazyGetMetamaskNonceQuery();
const onboarding = useRef<Onboarding | undefined>();
const onboarding = useRef<MetaMaskOnboarding | undefined>();
useEffect(() => {
if (!onboarding.current) {
@@ -94,9 +93,9 @@ export default function MetamaskLoginButton({
}
};
return (
<StyledSocialButton disabled={requesting} onClick={handleMetamaskLogin}>
<img className="icon" src={metamaskSvg} alt="meta mask icon" />
<Button className="flex ghost flex-center gap-2 !text-gray-600 !border-slate-200 dark:!text-gray-100" disabled={requesting} onClick={handleMetamaskLogin}>
<img className="w-6 h-6" src={metamaskSvg} alt="meta mask icon" />
{type == "login" ? t("login.metamask") : t("reg.metamask")}
</StyledSocialButton>
</Button>
);
}
+9 -28
View File
@@ -1,68 +1,49 @@
/* eslint-disable no-undef */
import { FC, useState } from "react";
import styled from "styled-components";
import StyledModal from "../../common/component/styled/Modal";
import Modal from "../../common/component/Modal";
import { StyledSocialButton } from "./styled";
import StyledButton from "../../common/component/styled/Button";
import OidcLoginEntry from "./OidcLoginEntry";
import { OIDCConfig } from "../../types/auth";
import { useTranslation } from "react-i18next";
import { AuthType } from "../../types/common";
const StyledOidcLoginModal = styled(StyledModal)`
text-align: center;
padding: 32px 32px 16px;
> *:first-child {
margin-bottom: 32px;
}
> .button {
> .icon {
width: 24px;
height: 24px;
}
&.buttonCancel {
color: #8f8f8f;
}
}
`;
import Button from "../../common/component/styled/Button";
interface IProps {
issuers?: OIDCConfig[];
type?: AuthType
}
const OidcLoginButton: FC<IProps> = ({ issuers, type = "login" }) => {
const { t } = useTranslation("auth");
const { t: ct } = useTranslation();
const [modal, setModal] = useState(false);
if (!issuers) return null;
return (
<>
<StyledSocialButton
<Button
className="flex ghost flex-center gap-2 !text-gray-600 !border-slate-200 dark:!text-gray-100"
onClick={() => {
setModal(true);
}}
>
{type == "login" ? t("login.oidc") : t("reg.oidc")}
</StyledSocialButton>
</Button>
{modal && (
<Modal id="modal-modal">
<StyledOidcLoginModal title="Login with OIDC">
<StyledModal className="text-center " title="Login with OIDC">
{issuers
.filter((issuer) => issuer.enable)
.map((issuer, index) => (
<OidcLoginEntry issuer={issuer} key={index} />
))}
<StyledButton
className="border_less ghost buttonCancel"
className="border_less ghost text-gray-500 dark:text-white"
onClick={() => {
setModal(false);
}}
>
Close
{ct("action.close")}
</StyledButton>
</StyledOidcLoginModal>
</StyledModal>
</Modal>
)}
</>
+4 -4
View File
@@ -1,7 +1,7 @@
import { useEffect, FC } from "react";
import { useGetOpenidMutation } from "../../app/services/auth";
import Button from "../../common/component/styled/Button";
import { OIDCConfig } from "../../types/auth";
import { StyledSocialButton } from "./styled";
const OidcLoginEntry: FC<{ issuer: OIDCConfig }> = ({ issuer }) => {
const [getOpenId, { data, isLoading, isSuccess }] = useGetOpenidMutation();
@@ -21,10 +21,10 @@ const OidcLoginEntry: FC<{ issuer: OIDCConfig }> = ({ issuer }) => {
}, [data, isSuccess]);
return (
<StyledSocialButton disabled={isLoading || isSuccess} onClick={handleSolidLogin}>
{!!issuer.favicon && <img src={issuer.favicon} className="icon" alt="icon" />}
<Button className="flex text-gray-800 dark:text-gray-100 flex-center gap-3" disabled={isLoading || isSuccess} onClick={handleSolidLogin}>
{!!issuer.favicon && <img src={issuer.favicon} className="w-6 h-6" alt="icon" />}
{isLoading ? `Loading...` : `Login with ${issuer.domain}`}
</StyledSocialButton>
</Button>
);
};
export default OidcLoginEntry;
+1 -1
View File
@@ -17,7 +17,7 @@ export default function MagicLinkLogin({ smtp = false }: Props) {
};
return (
<div className="flex gap-1 mt-7 text-sm text-[#667085] justify-center">
<div className="flex gap-1 mt-7 text-sm text-[#667085] dark:text-gray-100 justify-center">
<span>{t("login.no_account")}</span>
<a className="text-[#22d3ee] cursor-pointer" onClick={handleSignUp}>{t("sign_up")}</a>
</div>
+12 -11
View File
@@ -3,7 +3,6 @@ import { useState, useEffect, FormEvent, ChangeEvent } from "react";
import toast from "react-hot-toast";
import BASE_URL from "../../app/config";
// import web3 from "web3";
import StyledWrapper from "./styled";
import Input from "../../common/component/styled/Input";
import Button from "../../common/component/styled/Button";
@@ -15,6 +14,7 @@ import useGoogleAuthConfig from "../../common/hook/useGoogleAuthConfig";
import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query";
import { useTranslation } from "react-i18next";
import SocialLoginButtons from "./SocialLoginButtons";
import Divider from "../../common/component/Divider";
export default function LoginPage() {
const { t } = useTranslation("auth");
@@ -122,15 +122,16 @@ export default function LoginPage() {
const hasDivider =
enableMagicLink || googleLogin || enableMetamaskLogin || oidc.length > 0 || enableGithubLogin;
return (
<StyledWrapper>
<div className="form">
<div className="tips">
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
<h2 className="title">{t("login.title")}</h2>
<span className="desc">{t("login.desc")}</span>
<div className="flex-center h-screen dark:bg-[#384250]">
<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" />
<h2 className="font-semibold text-2xl text-gray-800 dark:text-white mb-2">{t("login.title")}</h2>
<span className="text-gray-400 dark:text-gray-100">{t("login.desc")}</span>
</div>
<form onSubmit={handleLogin}>
<form className="flex flex-col gap-5 min-w-[360px]" onSubmit={handleLogin}>
<Input
className="large"
name="email"
@@ -155,8 +156,8 @@ export default function LoginPage() {
{isLoading ? "Signing" : t("sign_in")}
</Button>
</form>
{hasDivider && <hr className="or" />}
<div className="btns">
{hasDivider && <Divider content="OR" />}
<div className="flex flex-col gap-4">
{enableMagicLink && <MagicLinkLogin />}
@@ -164,6 +165,6 @@ export default function LoginPage() {
</div>
{whoCanSignUp === "EveryOne" && <SignUpLink smtp={enableSMTP} />}
</div>
</StyledWrapper>
</div>
);
}
-88
View File
@@ -1,88 +0,0 @@
import styled from "styled-components";
import Button from "../../common/component/styled/Button";
export const StyledSocialButton = styled(Button)`
width: 100%;
/* margin-bottom: 16px; */
display: flex;
align-items: center;
justify-content: center;
gap: 12px;
color: #344054;
border: 1px solid #d0d5dd;
background: none !important;
.icon {
width: 24px;
height: 24px;
}
`;
const StyledWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
.form {
padding: 36px 40px 32px 40px;
box-shadow: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
border-radius: 12px;
.tips {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 24px;
.logo {
width: 56px;
height: 56px;
margin-bottom: 28px;
border-radius: 50%;
}
.title {
font-weight: 600;
font-size: 24px;
line-height: 32px;
color: #101828;
margin-bottom: 8px;
}
}
.desc {
font-weight: normal;
font-size: 16px;
line-height: 24px;
color: #667085;
}
form {
display: flex;
flex-direction: column;
gap: 20px;
width: 360px;
}
.or {
border: none;
position: relative;
height: 1px;
background-color: #e4e7ec;
margin: 26px 0;
overflow: visible;
&:after {
padding: 4px;
background-color: #fff;
content: "OR";
position: absolute;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
font-size: 14px;
line-height: 20px;
color: #667085;
}
}
>.btns{
display: flex;
flex-direction: column;
gap: 15px;
}
}
`;
export default StyledWrapper;
@@ -1,6 +1,5 @@
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import styled from "styled-components";
import StyledRadio from "../../../common/component/styled/Radio";
import StyledButton from "../../../common/component/styled/Button";
import { useGetLoginConfigQuery, useUpdateLoginConfigMutation } from "../../../app/services/server";
@@ -40,20 +39,9 @@ export default function SignUpSetting() {
if (isSuccess) nextStep();
}, [isSuccess]);
const StyledWrapper = styled.div`
/* > form {
width: 512px;
}
> .button {
width: 124px;
height: 44px;
margin-top: 24px;
} */
`;
return (
<StyledWrapper className="h-full flex-center flex-col text-center w-[512px] m-auto">
<div className="h-full flex-center flex-col text-center w-[512px] m-auto">
<span className="font-bold text-2xl mb-2">{t("onboarding.invite_title")}</span>
<span className="text-sm mb-6">{t("onboarding.invite_desc")}</span>
{value && <StyledRadio
@@ -82,6 +70,6 @@ export default function SignUpSetting() {
>
{t("onboarding.confirm")}
</StyledButton>
</StyledWrapper>
</div>
);
}
+3 -3
View File
@@ -2,9 +2,9 @@
export default function EmailNextTip() {
return (
<div className="flex flex-col items-center">
<div className="font-bold text-3xl text-gray-800 mt-3">Magic link Sent</div>
<p className="text-center text-gray-400 mb-6">Login to your email client, and continue next step</p>
<p className="text-center text-gray-400">You can close this window now.</p>
<div className="font-bold text-3xl text-gray-800 dark:text-white mt-3">Magic link Sent</div>
<p className="text-center text-gray-400 dark:text-gray-300 mb-6">Login to your email client, and continue next step</p>
<p className="text-center text-gray-400 dark:text-gray-300">You can close this window now.</p>
</div>
);
}
+1 -1
View File
@@ -2,7 +2,7 @@
export default function ExpiredTip() {
return (
<div className="flex flex-col items-center">
<div className="font-bold text-3xl text-gray-800 mt-3">Magic link expired</div>
<div className="font-bold text-3xl text-gray-800 dark:text-white mt-3">Magic link expired</div>
<p className="text-center text-gray-400 mb-6">Go back to your original VoceChat tab and request a new magic link.</p>
<p className="text-center text-gray-400">You can close this window now.</p>
</div>
+4 -4
View File
@@ -98,14 +98,14 @@ const RegWithUsername: FC = () => {
const isSuccess = loginSuccess || regSuccess;
return (
<>
<div className="tips">
<h2 className="title">Whats your name</h2>
<span className="desc">
<div className="flex-center flex-col pb-6">
<h2 className="font-semibold text-2xl text-gray-800 dark:text-white mb-2">Whats your name</h2>
<span className="text-gray-400 dark:text-gray-100">
Enter a name or handle so people know how youd like to be called. Your name will only be
visible to others in spaces you joined.
</span>
</div>
<form onSubmit={handleAuth}>
<form className="flex flex-col gap-5 min-w-[360px]" onSubmit={handleAuth}>
<Input
className="large"
name="username"
+7 -6
View File
@@ -6,6 +6,7 @@ import Button from "../../common/component/styled/Button";
import { useLazyCheckEmailQuery, useSendRegMagicLinkMutation } from "../../app/services/auth";
import EmailNextTip from "./EmailNextStepTip";
import SignInLink from "./SignInLink";
import Divider from "../../common/component/Divider";
import { useTranslation } from "react-i18next";
import { useGetLoginConfigQuery } from "../../app/services/server";
@@ -101,13 +102,13 @@ export default function Reg() {
return (
<>
<div className="tips">
<img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
<h2 className="title">{t("reg.title")}</h2>
<span className="desc">{t("reg.desc")}</span>
<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>
</div>
<form onSubmit={handleReg} autoSave={"false"} autoComplete={"true"}>
<form className="flex flex-col gap-5 min-w-[360px]" onSubmit={handleReg} autoSave={"false"} autoComplete={"true"}>
<Input
className="large"
name="email"
@@ -145,7 +146,7 @@ export default function Reg() {
{isLoading ? t('signing_up') : t("sign_up")}
</Button>
</form>
<hr className="or" />
<Divider content="OR" />
<div className="flex flex-col gap-3 py-3">
<SocialLoginButtons type="register" />
</div>
+1 -1
View File
@@ -17,7 +17,7 @@ export default function SignInLink({ token }: { token?: string }) {
}, [token]);
return (
<div className="flex gap-1 mt-7 text-sm text-[#667085] justify-center">
<div className="flex gap-1 mt-7 text-sm text-[#667085] dark:text-gray-100 justify-center">
<span>{t("reg.have_account")}</span>
<a onClick={handleSignIn} className="text-[#22d3ee] cursor-pointer">{t("sign_in")}</a>
</div>
+3 -4
View File
@@ -1,12 +1,11 @@
import { Outlet } from "react-router-dom";
import StyledWrapper from "./styled";
export default function RegContainer() {
return (
<StyledWrapper>
<div className="form">
<div className="flex-center h-screen dark:bg-[#384250]">
<div className="py-8 px-10 shadow-md rounded-xl">
<Outlet />
</div>
</StyledWrapper>
</div>
);
}
-71
View File
@@ -1,71 +0,0 @@
import styled from "styled-components";
const StyledWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
.form {
max-width: 440px;
padding: 36px 40px 32px 40px;
box-shadow: 0 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
border-radius: 12px;
.tips {
display: flex;
flex-direction: column;
align-items: center;
padding-bottom: 24px;
.logo {
width: 56px;
height: 56px;
margin-bottom: 28px;
border-radius: 50%;
}
.title {
font-weight: 600;
font-size: 24px;
line-height: 32px;
color: #101828;
margin-bottom: 8px;
&.error {
color: red;
}
}
.desc {
text-align: center;
font-weight: normal;
font-size: 16px;
line-height: 24px;
color: #667085;
}
}
form {
display: flex;
flex-direction: column;
gap: 20px;
width: 360px;
}
}
.or {
border: none;
position: relative;
height: 1px;
background-color: #e4e7ec;
margin: 26px 0;
overflow: visible;
&:after {
padding: 4px;
background-color: #fff;
content: "OR";
position: absolute;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
font-size: 14px;
line-height: 20px;
color: #667085;
}
}
`;
export default StyledWrapper;
+10 -58
View File
@@ -1,57 +1,9 @@
import { FC } from "react";
import styled from "styled-components";
import CheckSign from "../../../assets/icons/check.sign.svg";
import ChannelIcon from "../../../common/component/ChannelIcon";
import Search from "../Search";
import useFilteredChannels from "../../../common/hook/useFilteredChannels";
const Styled = styled.div`
padding: 0 4px 4px 4px;
background: #ffffff;
max-height: 400px;
overflow: auto;
box-shadow: 0 24px 48px -12px rgba(16, 24, 40, 0.18);
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
> .search {
z-index: 1;
background-color: #fff;
position: sticky;
top: 0;
input {
z-index: 2;
}
}
> .list {
width: 100%;
display: flex;
flex-direction: column;
gap: 16px;
padding: 8px;
.channel {
position: relative;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
.name {
color: #616161;
font-weight: 600;
font-size: 14px;
line-height: 20px;
}
.check {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
}
}
`;
type Props = {
select: number;
updateFilter: (param: { channel?: number }) => void;
@@ -63,27 +15,27 @@ const Channel: FC<Props> = ({ select = 0, updateFilter }) => {
};
return (
<Styled>
<div className="search">
<div className="rounded-lg p-1 pt-0 bg-white dark:bg-gray-800 overflow-auto max-h-[400px] flex flex-col items-start relative drop-shadow">
<div className=" bg-white dark:bg-gray-800 sticky top-0 z-10">
<Search embed={true} value={input} updateSearchValue={updateInput} />
</div>
<ul className="list">
<li className="channel" onClick={handleClick.bind(null, undefined)}>
<ul className="w-full flex flex-col gap-4 p-2">
<li className="relative cursor-pointer flex items-center gap-2" onClick={handleClick.bind(null, undefined)}>
<ChannelIcon />
<span className="name">Any Channel</span>
{!select && <CheckSign className="check" />}
<span className="text-gray-500 dark:text-gray-100 font-semibold text-sm">Any Channel</span>
{!select && <CheckSign className="absolute right-0 top-1/2 -translate-y-1/2" />}
</li>
{channels.map(({ gid, is_public, name }) => {
return (
<li key={gid} className="channel" onClick={handleClick.bind(null, gid)}>
<li key={gid} className="relative cursor-pointer flex items-center gap-2" onClick={handleClick.bind(null, gid)}>
<ChannelIcon personal={!is_public} />
<span className="name">{name}</span>
{select == gid && <CheckSign className="check" />}
<span className="text-gray-500 dark:text-gray-100 font-semibold text-sm">{name}</span>
{select == gid && <CheckSign className="absolute right-0 top-1/2 -translate-y-1/2" />}
</li>
);
})}
</ul>
</Styled>
</div>
);
};
export default Channel;
+7 -43
View File
@@ -1,42 +1,6 @@
import { FC } from "react";
import styled from "styled-components";
import CheckSign from "../../../assets/icons/check.sign.svg";
const Styled = styled.div`
padding: 12px;
background: #ffffff;
min-width: 200px;
overflow: auto;
box-shadow: 0px 24px 48px -12px rgba(16, 24, 40, 0.18);
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
> .list {
width: 100%;
display: flex;
flex-direction: column;
gap: 16px;
.date {
position: relative;
cursor: pointer;
display: flex;
align-items: center;
gap: 16px;
color: #616161;
font-weight: 600;
font-size: 14px;
line-height: 20px;
.check {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
}
}
`;
export const Dates = {
today: {
@@ -66,22 +30,22 @@ const DateFilter: FC<Props> = ({ select = "", updateFilter }) => {
};
return (
<Styled>
<ul className="list">
<li className="date" onClick={handleClick.bind(null, undefined)}>
<div className="p-3 bg-white min-w-[200px] overflow-auto rounded-lg flex flex-col items-start relative drop-shadow">
<ul className="w-full flex flex-col gap-4">
<li className="relative cursor-pointer flex items-center gap-4 text-gray-500 font-semibold text-sm" onClick={handleClick.bind(null, undefined)}>
Any Time
{!select && <CheckSign className="check" />}
{!select && <CheckSign className="absolute right-0 top-1/2 -translate-y-1/2" />}
</li>
{Object.entries(Dates).map(([_key, { title }]) => {
return (
<li key={title} className="date" onClick={handleClick.bind(null, _key)}>
<li key={title} className="relative cursor-pointer flex items-center gap-4 text-gray-500 font-semibold text-sm" onClick={handleClick.bind(null, _key)}>
{title}
{select == _key && <CheckSign className="check" />}
{select == _key && <CheckSign className="absolute right-0 -top-1/2 -translate-y-1/2" />}
</li>
);
})}
</ul>
</Styled>
</div>
);
};
export default DateFilter;
+8 -52
View File
@@ -1,53 +1,9 @@
import { FC } from "react";
import styled from "styled-components";
import Search from "../Search";
import CheckSign from "../../../assets/icons/check.sign.svg";
import User from "../../../common/component/User";
import useFilteredUsers from "../../../common/hook/useFilteredUsers";
const Styled = styled.div`
padding: 0 4px 4px 4px;
background: #ffffff;
max-height: 300px;
overflow: auto;
box-shadow: 0 24px 48px -12px rgba(16, 24, 40, 0.18);
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: flex-start;
position: relative;
> .search {
z-index: 1;
background-color: #fff;
position: sticky;
top: 0;
input {
z-index: 2;
}
}
> .list {
width: 100%;
display: flex;
flex-direction: column;
.user {
position: relative;
cursor: pointer;
&.none {
padding: 10px;
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #616161;
}
.check {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
}
}
}
`;
type Props = {
select: number;
updateFilter: (param: { from?: number }) => void;
@@ -59,25 +15,25 @@ const From: FC<Props> = ({ select = "", updateFilter }) => {
};
return (
<Styled>
<div className="search">
<div className="rounded-lg p-1 pt-0 bg-white dark:bg-gray-800 overflow-auto max-h-[300px] flex flex-col items-start relative drop-shadow">
<div className="bg-white sticky top-0 z-10">
<Search embed={true} value={input} updateSearchValue={updateInput} />
</div>
<ul className="list">
<li className="user none" onClick={handleClick.bind(null, undefined)}>
<ul className="w-full flex flex-col">
<li className="relative cursor-pointer p-2.5 font-semibold text-sm text-gray-500" onClick={handleClick.bind(null, undefined)}>
Anyone
{!select && <CheckSign className="check" />}
{!select && <CheckSign className="absolute right-1.5 top-1/2 -translate-y-1/2" />}
</li>
{users.map(({ uid }) => {
return (
<li key={uid} className="user" onClick={handleClick.bind(null, uid)}>
<li key={uid} className="relative cursor-pointer" onClick={handleClick.bind(null, uid)}>
<User uid={uid} interactive={true} />
{select == uid && <CheckSign className="check" />}
{select == uid && <CheckSign className="absolute right-1.5 top-1/2 -translate-y-1/2" />}
</li>
);
})}
</ul>
</Styled>
</div>
);
};
export default From;
+4 -4
View File
@@ -48,17 +48,17 @@ const Type: FC<Props> = ({ select = "", updateFilter }) => {
};
return (
<div className="p-3 bg-white min-w-[100px] overflow-auto shadow-md rounded-lg flex flex-col items-start relative">
<div className="p-3 bg-white min-w-[180px] overflow-auto shadow-md rounded-lg flex flex-col items-start relative">
<ul className="w-full flex flex-col gap-4">
<li className="flex justify-between" onClick={handleClick.bind(null, undefined)}>
<li className="relative cursor-pointer flex items-center gap-4 text-gray-500 font-semibold text-sm" onClick={handleClick.bind(null, undefined)}>
Any Type
{!select && <CheckSign className="check" />}
{!select && <CheckSign className="absolute right-0 top-1/2 -translate-y-1/2" />}
</li>
{Object.entries(FileTypes).map(([type, { title, icon }]) => {
return (
<li key={title} className="relative cursor-pointer flex items-center gap-2 text-sm text-gray-500 font-semibold" onClick={handleClick.bind(null, type)}>
{icon} {title}
{select == type && <CheckSign className="absolute right-0 left-1/2 -translate-y-1/2" />}
{select == type && <CheckSign className="absolute right-0 top-1/2 -translate-y-1/2" />}
</li>
);
})}
+12 -41
View File
@@ -1,5 +1,4 @@
import { useState } from "react";
import styled from "styled-components";
import Tippy from "@tippyjs/react";
import Avatar from "../../../common/component/Avatar";
import FilterDate, { Dates } from "./Date";
@@ -9,41 +8,12 @@ import FilterType, { FileTypes } from "./Type";
import ArrowDown from "../../../assets/icons/arrow.down.svg";
import { useAppSelector } from "../../../app/store";
import { useTranslation } from "react-i18next";
import clsx from "clsx";
const Styled = styled.div`
/* padding: 20px 0; */
display: flex;
align-items: center;
gap: 8px;
.filter {
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
border: 1px solid #d0d5dd;
box-sizing: border-box;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
border-radius: var(--br);
padding: 7px 12px;
font-weight: 500;
font-size: 12px;
line-height: 18px;
&.selected {
border: none;
color: #fff;
background-color: #22ccee;
.arrow path {
stroke: #fff;
}
}
.avatar {
width: 16px;
height: 16px;
border-radius: 50%;
}
}
`;
const getClass = (selected: boolean) => {
return clsx(`cursor-pointer flex items-center gap-2 border border-solid box-border shadow rounded-lg py-2 px-3 text-xs`, selected && 'text-white bg-primary-400');
};
export default function Filter({ filter, updateFilter }) {
const { t } = useTranslation("file");
const [filtersVisible, setFiltersVisible] = useState({
@@ -79,8 +49,9 @@ export default function Filter({ filter, updateFilter }) {
from: fromVisible
} = filtersVisible;
return (
<Styled>
<div className="flex items-center gap-2">
<Tippy
interactive
onClickOutside={toggleFilterVisible.bind(null, { from: false })}
@@ -89,14 +60,14 @@ export default function Filter({ filter, updateFilter }) {
content={<FilterFrom select={filter.from} updateFilter={handleUpdateFilter} />}
>
<div
className={`filter ${from && "selected"}`}
className={getClass(from)}
onClick={toggleFilterVisible.bind(null, { from: true })}
>
{from && (
<Avatar
width={16}
height={16}
className="avatar"
className="w-4 h-4 rounded-full"
name={userMap[from].name}
src={userMap[from].avatar}
/>
@@ -115,7 +86,7 @@ export default function Filter({ filter, updateFilter }) {
content={<FilterChannel select={filter.channel} updateFilter={handleUpdateFilter} />}
>
<div
className={`filter ${channel && "selected"}`}
className={getClass(channel)}
onClick={toggleFilterVisible.bind(null, { channel: true })}
>
<span className="txt">{channel ? `In ${channelMap[channel].name}` : t("channel")}</span>
@@ -130,7 +101,7 @@ export default function Filter({ filter, updateFilter }) {
content={<FilterType select={filter.type} updateFilter={handleUpdateFilter} />}
>
<div
className={`filter ${type && "selected"}`}
className={getClass(type)}
onClick={toggleFilterVisible.bind(null, { type: true })}
>
<span className="txt">{type ? FileTypes[type].title : t("type")}</span>
@@ -145,13 +116,13 @@ export default function Filter({ filter, updateFilter }) {
content={<FilterDate select={filter.date} updateFilter={handleUpdateFilter} />}
>
<div
className={`filter ${date && "selected"}`}
className={getClass(date)}
onClick={toggleFilterVisible.bind(null, { date: true })}
>
<span className="txt">{date ? Dates[date].title : t("date")}</span>
<ArrowDown className="arrow" />
</div>
</Tippy>
</Styled>
</div>
);
}
+13 -38
View File
@@ -1,55 +1,30 @@
import { MouseEvent } from "react";
import styled from "styled-components";
import { useDispatch } from "react-redux";
import { updateFileListView } from "../../app/slices/ui";
import IconList from "../../assets/icons/file.list.svg";
import IconGrid from "../../assets/icons/file.grid.svg";
import clsx from "clsx";
const Styled = styled.ul`
display: flex;
border: 1px solid #d0d5dd;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
box-sizing: border-box;
.view {
cursor: pointer;
padding: 8px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
&.item .item,
&.grid .grid {
border: 1px solid #52edff;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
svg {
transition: all 0.2s ease;
path {
fill: #52edff;
}
}
}
`;
export default function View({ view = "item" }) {
const getClass = (selected: boolean) => clsx(`cursor-pointer p-2 box-border flex-center`, selected && `border border-solid border-primary-400 shadow rounded-lg`);
type Props = {
view?: "item" | "grid"
}
export default function View({ view = "item" }: Props) {
const dispatch = useDispatch();
const handleChangeView = (evt: MouseEvent<HTMLLIElement>) => {
const { view: clickView } = evt.currentTarget.dataset;
if (clickView == view) return;
dispatch(updateFileListView(view == "item" ? "grid" : "item"));
};
const isGrid = view == "grid";
return (
<Styled className={view}>
<li className="view item" data-view={"item"} onClick={handleChangeView}>
<IconList />
<ul className={`flex border border-solid shadow rounded-lg box-border`}>
<li className={getClass(!isGrid)} data-view={"item"} onClick={handleChangeView}>
<IconList className={`${!isGrid ? "fill-primary-400" : ""} dark:fill-white`} />
</li>
<li className="view grid" data-view={"grid"} onClick={handleChangeView}>
<IconGrid />
<li className={getClass(isGrid)} data-view={"grid"} onClick={handleChangeView}>
<IconGrid className={`${isGrid ? "fill-primary-400" : ""} dark:fill-white`} />
</li>
</Styled>
</ul>
);
}
+1 -1
View File
@@ -82,7 +82,7 @@ function ResourceManagement({ fileMessages }) {
}, [view, filter]);
return (
<div className="h-screen overflow-y-scroll flex flex-col items-start my-2 mr-6 rounded-2xl bg-white">
<div className="h-screen overflow-y-scroll flex flex-col items-start my-2 mr-6 rounded-2xl bg-white dark:bg-[#384250]">
<Search value={filter.name} updateSearchValue={handleUpdateSearch} />
<div className="divider"></div>
<div className="flex justify-between w-full px-4 py-5">
-40
View File
@@ -1,40 +0,0 @@
import styled from "styled-components";
const Styled = styled.div`
height: 100vh;
overflow-y: scroll;
display: flex;
flex-direction: column;
align-items: flex-start;
background-color: #fff;
margin: 8px 24px 10px 0;
border-radius: 16px;
.opts {
padding: 20px 16px;
display: flex;
justify-content: space-between;
width: 100%;
}
> .list {
padding: 0 16px;
height: 100%;
overflow-y: scroll;
width: 100%;
display: flex;
&.item {
gap: 8px;
flex-direction: column;
}
&.grid {
flex-direction: row;
flex-wrap: wrap;
> .file_box {
flex-direction: column;
/* margin-right: 8px; */
margin-bottom: 8px;
}
}
}
`;
export default Styled;
+1 -1
View File
@@ -40,7 +40,7 @@ export default function APIConfig() {
data-checked={thirdParty}
/>
<div className="w-full flex flex-col items-start gap-2">
<label htmlFor="secret" className="text-sm text-gray-500"> {t("third_app.key")}:</label>
<label htmlFor="secret" className="text-sm text-gray-500 dark:text-gray-100"> {t("third_app.key")}:</label>
<Input disabled={!thirdParty} type="password" id="secret" value={updatedSecret || data} />
</div>
<Tippy
+7 -7
View File
@@ -14,11 +14,11 @@ const APIDocument = () => {
};
return (
<section className="flex flex-col justify-start items-start gap-4">
<div className="text-gray-500 ">
<div className="text-gray-500 dark:text-gray-100">
{t("api_doc.desc")}
</div>
<div className='flex flex-col gap-2'>
<h2 className='text-gray-700 text-xl font-bold'>
<h2 className='text-gray-700 dark:text-white text-xl font-bold'>
{t("api_doc.access")}
</h2>
<a href={APIUrl} target="_blank" rel="noopener noreferrer" className='underline text-primary-600'>
@@ -26,12 +26,12 @@ const APIDocument = () => {
</a>
</div>
<div className='flex flex-col gap-2'>
<h2 className='text-gray-700 text-xl font-bold'>
<h2 className='text-gray-700 dark:text-white text-xl font-bold'>
{t("api_doc.use_method")}
</h2>
<div className=" flex flex-col gap-6">
<div className="flex flex-col gap-2">
<h3 className='text-gray-700 text-lg'>
<h3 className='text-gray-700 dark:text-white text-lg'>
👉 {t("api_doc.step_1")}
</h3>
{/* <div className="flex flex-col gap-1"> */}
@@ -39,8 +39,8 @@ const APIDocument = () => {
{/* </div> */}
</div>
<div className="flex flex-col gap-2">
<h3 className='text-gray-700 text-lg flex items-center gap-2'>
👉 {t("api_doc.step_2")} <span className='text-gray-500 text-sm'>
<h3 className='text-gray-700 dark:text-white text-lg flex items-center gap-2'>
👉 {t("api_doc.step_2")} <span className='text-gray-500 dark:text-white text-sm'>
({t("api_doc.step_2_desc")})
</span>
</h3>
@@ -53,7 +53,7 @@ const APIDocument = () => {
<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" />
</div>
<div className="flex flex-col gap-2">
<h3 className='text-gray-700 text-lg flex items-center 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" />
+3 -3
View File
@@ -21,7 +21,7 @@ type Props = {
// </table>;
// };
type DeleteAPIKeyProps = { uid: number, kid: number }
const tdClass = "p-1 whitespace-nowrap text-xs text-gray-500 align-middle px-1";
const tdClass = "p-1 whitespace-nowrap text-xs text-gray-500 dark:text-gray-200 align-middle px-1";
const BotAPIKeys = ({ uid }: Props) => {
const { t } = useTranslation("setting", { keyPrefix: "bot" });
const [currentUid, setCurrentUid] = useState<number | undefined>();
@@ -39,11 +39,11 @@ const BotAPIKeys = ({ uid }: Props) => {
const colWidths = ["w-20", "w-[166px]", "w-36", "w-15", "w-10"];
return (
<div className='flex flex-col gap-2 items-start'>
<div className='border-t border-solid border-b border-gray-100 py-2 w-full'>
<div className='border-t border-solid border-b border-gray-100 dark:border-gray-500 py-2 w-full'>
<table className="min-w-full table-fixed font-mono">
<thead >
<tr >
{[t("col_key_name"), t('col_key_value'), t('col_key_create_time'), t('col_key_last_used'), ""].map((title, idx) => <th key={title} scope="col" className={clsx(`text-xs text-gray-900 px-1 text-left pb-2`, colWidths[idx])}>
{[t("col_key_name"), t('col_key_value'), t('col_key_create_time'), t('col_key_last_used'), ""].map((title, idx) => <th key={title} scope="col" className={clsx(`text-xs text-gray-900 dark:text-gray-50 px-1 text-left pb-2`, colWidths[idx])}>
{title}
</th>)}
</tr>
+3 -3
View File
@@ -77,12 +77,12 @@ const CreateModal = ({ closeModal }: Props) => {
</>
}
>
<form ref={formRef} className="w-full flex flex-col gap-2 items-center" action="/">
<div className="flex flex-col gap-1 w-full">
<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>
<Input name={"name"} required placeholder='Please input bot name'></Input>
</div>
<div className="flex flex-col gap-1 w-full">
<div className="flex flex-col items-start gap-1 w-full">
<label htmlFor={"webhook_url"} className="text-sm text-[#6b7280]">Webhook URL (Optional)</label>
<Input name={"webhook_url"} type="url" placeholder='Please input webhook url'></Input>
</div>
+1 -1
View File
@@ -72,7 +72,7 @@ const WebhookEdit = ({ uid }: Props) => {
evt.preventDefault();
handleEdit();
}}>
<input readOnly={!editable} required autoFocus type="url" name='webhook' defaultValue={url} className={clsx("text-sm text-gray-500 px-2 py-1", editable ? "border border-solid border-gray-200 bg-gray-50" : "bg-transparent")} />
<input readOnly={!editable} required autoFocus type="url" name='webhook' defaultValue={url} className={clsx("text-sm text-gray-500 dark:text-gray-100 px-2 py-1", editable ? "border border-solid border-gray-200 bg-gray-50" : "bg-transparent")} />
</form>
<button type='button' disabled={isUpdating} onClick={handleEdit}>
{isUpdating ? <Orbit size={16} /> : editable ?
+8 -9
View File
@@ -20,11 +20,11 @@ type TipProps = { title: string, desc: string };
const Tip = ({ title, desc }: TipProps) => {
return <div className="flex flex-col text-sm">
<h2 className="">{title}</h2>
<p className="text-gray-500">{desc}</p>
<h2 className="dark:text-white">{title}</h2>
<p className="text-gray-500 dark:text-gray-400">{desc}</p>
</div>;
};
const tdClass = "p-6 whitespace-nowrap text-sm font-medium text-gray-900 align-top";
const tdClass = "p-6 whitespace-nowrap text-sm font-medium text-gray-900 dark:text-gray-100 align-top";
type WebhookParams = { webhook?: string, uid: number };
type DeleteParams = { name: string, uid: number };
export default function BotConfig() {
@@ -65,19 +65,19 @@ export default function BotConfig() {
<Tip title={t("webhook_tip_title")} desc={t("webhook_tip_desc")} />
</div>
<div className="flex flex-col gap-1">
<h2 className='font-semibold flex gap-4 items-center'>
<h2 className='font-semibold flex gap-4 items-center dark:text-white'>
{t("manage")}
<a href="https://doc.voce.chat/bot-and-webhook" target="_blank" className="text-sm text-blue-400 underline-offset-1 underline" rel="noopener noreferrer">
🔗 {t("how_to_use")}
</a>
</h2>
<p className='text-gray-500 text-xs'>{t("manage_desc")}</p>
<p className='text-gray-500 dark:text-gray-400 text-xs'>{t("manage_desc")}</p>
</div>
<div className="w-fit overflow-hidden">
<table className="min-w-full table-auto">
<thead className="border-b bg-gray-50">
<thead className="border-b dark:border-b-gray-500 bg-gray-50 dark:bg-gray-600">
<tr>
{[t("col_avatar"), t('col_name'), t('col_api_key'), t('col_webhook'), t('col_opt')].map(title => <th key={title} scope="col" className="text-sm font-bold text-gray-900 px-6 py-4 text-left">
{[t("col_avatar"), t('col_name'), t('col_api_key'), t('col_webhook'), t('col_opt')].map(title => <th key={title} scope="col" className="text-sm font-bold text-gray-900 dark:text-gray-100 px-6 py-4 text-left">
{title}
</th>)}
</tr>
@@ -85,10 +85,9 @@ export default function BotConfig() {
<tbody>
{bots.map(bot => {
const { uid, name, avatar } = bot;
return <tr key={uid} className="bg-white border-b transition duration-300 ease-in-out hover:bg-gray-100">
return <tr key={uid} className="bg-white dark:bg-gray-800 border-b dark:border-b-gray-500 transition duration-300 ease-in-out hover:bg-gray-100">
<td className="px-4 py-2">
<AvatarUploader uid={uid} url={avatar} uploadImage={updateAvatar} name={name} size={56} />
</td>
<td className={`${tdClass}`}>
<div>
+1 -1
View File
@@ -13,7 +13,7 @@ const Item = ({ label, data, foldable = false, ...rest }: ItemProps) => {
const infoClass = clsx("font-bold w-full cursor-pointer", foldable ? " overflow-hidden text-ellipsis" : "whitespace-pre-wrap break-all");
if (!data) return null;
return <div className="whitespace-nowrap flex flex-col items-start justify-start text-lg">
<span className="text-sm text-gray-400">{label}</span>
<span className="text-sm text-gray-400 dark:text-white">{label}</span>
{Array.isArray(data) ? <ul className={infoClass}>
{data.map((d) => {
return <li key={d}>{d}</li>;
+6 -6
View File
@@ -61,16 +61,16 @@ export default function MyAccount() {
return (
<>
<div className="flex flex-col items-start gap-8">
<div className="p-6 flex flex-col items-center w-[512px] bg-slate-100 rounded-2xl">
<div className="p-6 flex flex-col items-center w-[512px] bg-slate-100 dark:bg-gray-800 rounded-2xl">
<AvatarUploader url={avatar} name={name} uploadImage={uploadAvatar} />
<div className="mt-2 mb-16 font-bold text-lg text-gray-800">
<div className="mt-2 mb-16 font-bold text-lg text-gray-800 dark:text-white">
{name} <span className="font-normal text-gray-500">#{uid}</span>
</div>
<div className="w-full flex justify-between mb-6">
<div className="flex flex-col text-gray-500">
<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 ">
{name} <span className="text-gray-600"> #{uid}</span>
{name} <span className="text-gray-600 dark:text-gray-400"> #{uid}</span>
</span>
</div>
<Button data-edit="name" onClick={handleBasicEdit} className="">
@@ -78,7 +78,7 @@ export default function MyAccount() {
</Button>
</div>
<div className="w-full flex justify-between mb-6">
<div className="flex flex-col text-gray-500">
<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>
</div>
@@ -87,7 +87,7 @@ export default function MyAccount() {
</Button>
</div>
<div className="w-full flex justify-between mb-6">
<div className="flex flex-col text-gray-500">
<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>
</div>
+43
View File
@@ -0,0 +1,43 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import Radio from '../../../common/component/styled/Radio';
import { Theme } from '../../../types/common';
// type Props = {}
const DarkMode = () => {
const [theme, setTheme] = useState<Theme>(localStorage.theme || "auto");
const { t } = useTranslation("setting");
const handleThemeToggle = (v: Theme) => {
setTheme(v);
localStorage.theme = v;
// reset
document.documentElement.classList.remove("dark");
document.documentElement.classList.remove("light");
if (v !== "auto") {
document.documentElement.classList.add(v);
} else {
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.add(isDark ? "dark" : 'light');
}
};
return (
<div className="text-sm">
<p className="dark:text-gray-100 font-semibold">{t("overview.theme.title")}</p>
<p className="flex justify-between w-full text-gray-400 mb-2">
{t("overview.theme.desc")}
</p>
<Radio
options={[t("overview.theme.auto"), t("overview.theme.dark"), t("overview.theme.light")]}
values={['auto', 'dark', 'light']}
value={theme}
onChange={(v) => {
handleThemeToggle(v);
}}
/>
</div>
);
};
export default DarkMode;
+1 -2
View File
@@ -33,12 +33,11 @@ const Index = () => {
// if(!fetch)
return (
<div className="text-sm">
<p className="font-semibold">{t("overview.url.title")}</p>
<p className="dark:text-gray-100 font-semibold">{t("overview.url.title")}</p>
<p className="flex justify-between w-full text-gray-400">
{t("overview.url.desc")}
</p>
<div className="flex items-center gap-4 mt-2">
<StyledInput placeholder='frontend url' value={url} onChange={handleChange} />
<StyledButton disabled={!url || isLoading} className='' onClick={handleUpdate}> {ct("action.update")}</StyledButton>
</div>
+4 -2
View File
@@ -7,6 +7,7 @@ import useConfig from "../../../common/hook/useConfig";
import Server from './server';
import Language from './Language';
import FrontendURL from "./FrontendURL";
// import DarkMode from "./DarkMode";
import ServerVersionChecker from "../../../common/component/ServerVersionChecker";
export default function Overview() {
@@ -33,7 +34,7 @@ export default function Overview() {
{isAdmin && (
<>
<div className="text-sm">
<p className="font-semibold">{t("overview.sign_up.title")}</p>
<p className="dark:text-gray-100 font-semibold">{t("overview.sign_up.title")}</p>
<p className="flex justify-between w-full text-gray-400 mb-2">{t("overview.sign_up.desc")}</p>
<StyledRadio
options={[t("overview.sign_up.everyone"), t("overview.sign_up.invite")]}
@@ -45,7 +46,7 @@ export default function Overview() {
/>
</div>
<div className="text-sm">
<p className="font-semibold">{t("overview.guest_mode.title")}</p>
<p className="dark:text-gray-100 font-semibold">{t("overview.guest_mode.title")}</p>
<p className="flex justify-between w-full text-gray-400 mb-2">
<span className="txt">
{t("overview.guest_mode.desc")}
@@ -66,6 +67,7 @@ export default function Overview() {
</>
)}
<Language />
{/* <DarkMode /> */}
</div>
);
}
+1 -1
View File
@@ -11,7 +11,7 @@ const Index = () => {
};
return (
<div className="text-sm">
<p className="font-semibold">{t("overview.lang.title")}</p>
<p className="dark:text-gray-100 font-semibold">{t("overview.lang.title")}</p>
<p className="flex justify-between w-full text-gray-400 mb-2">
{t("overview.lang.desc")}
</p>
+3 -3
View File
@@ -72,7 +72,7 @@ const Index = () => {
</div>
{isAdmin && (
<div className="flex flex-col justify-between items-start">
<div className="text-sm text-gray-600">
<div className="text-sm text-gray-600 dark:text-gray-100">
{t("overview.upload_desc")}
</div>
</div>
@@ -80,7 +80,7 @@ const Index = () => {
</div>
<div className="flex flex-col items-start gap-6 mb-16">
<div className="w-full flex flex-col items-start gap-2">
<Label htmlFor="name">{t("overview.name")}</Label>
<Label className='dark:text-gray-100' htmlFor="name">{t("overview.name")}</Label>
<Input
disabled={!isAdmin}
data-type="name"
@@ -92,7 +92,7 @@ const Index = () => {
/>
</div>
<div className="w-full flex flex-col items-start gap-2">
<Label htmlFor="desc">{t("overview.desc")}</Label>
<Label className='dark:text-gray-100' htmlFor="desc">{t("overview.desc")}</Label>
<Textarea
disabled={!isAdmin}
data-type="description"
+12 -12
View File
@@ -5,14 +5,14 @@ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/esm/styles/prism';
const Row = ({ paramKey, paramDefault, remarks }: { paramKey: string, paramDefault: string | number, remarks: string }) => {
return <tr className="bg-white border-b transition duration-300 ease-in-out hover:bg-gray-100">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">
return <tr className="bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-50 border-b transition duration-300 ease-in-out hover:bg-gray-100">
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
{paramKey}
</td>
<td className="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap">
<td className="text-sm font-light px-6 py-4 whitespace-nowrap">
{paramDefault}
</td>
<td className="text-sm text-gray-900 font-light px-6 py-4 whitespace-nowrap">
<td className="text-sm font-light px-6 py-4 whitespace-nowrap">
{remarks}
</td>
</tr>;
@@ -22,23 +22,23 @@ export default function Widget() {
// const disableBtn = !reachLimit;
return (
<div className="flex flex-col justify-start items-start">
<div className="text-gray-600 ">
<div className="text-gray-600 dark:text-gray-100">
{t('tip')}
</div>
<label htmlFor="code" className="text-gray-500 text-sm mt-5">
<label htmlFor="code" className="text-gray-500 dark:text-white text-sm mt-5">
{t('code')}:
</label>
<SyntaxHighlighter id="code" language="html" style={vscDarkPlus} className="rounded">
{`<!-- ${t('code_comment')} -->\n<script \n data-host-id="1" \n data-close-width="48" \n data-close-height="48" \n data-open-width="380" \n data-open-height="680" \n src="${location.origin}/widget.js" \n async \n/>`}
</SyntaxHighlighter>
<div className="text-gray-500 text-sm mt-5 mb-2">
<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 rounded overflow-hidden">
<div className="w-[700px] border border-solid border-gray-300 dark:border-gray-400 rounded overflow-hidden">
<table className="min-w-full table-auto">
<thead className="border-b bg-gray-50">
<thead className="border-b bg-gray-50 dark:bg-gray-500">
<tr>
{[t('param_key'), t('default_value'), t('remark')].map(title => <th key={title} scope="col" className="text-sm font-bold text-gray-900 px-6 py-4 text-left">
{[t('param_key'), t('default_value'), t('remark')].map(title => <th key={title} scope="col" className="text-sm font-bold text-gray-900 dark:text-white px-6 py-4 text-left">
{title}
</th>)}
</tr>
@@ -67,9 +67,9 @@ export default function Widget() {
}
].map(row => <Row key={row.paramKey} {...row} />)}
</tbody>
<tfoot className="border-t border-solid border-gray-200" >
<tfoot className="border-t border-solid border-gray-200 dark:border-gray-50 dark:bg-gray-500" >
<tr>
<td colSpan={3} className="text-gray-400 px-5 py-3 text-sm">
<td colSpan={3} className="text-gray-400 dark:text-white px-5 py-3 text-sm">
* All the parameters are optional, and prefixed by <i className="bg-gray-700 text-white px-1">data-</i>
</td>
</tr>
+4 -4
View File
@@ -87,7 +87,7 @@ export default function ConfigFirebase() {
/>
{<fieldset className="inputs" disabled={select !== "custom"}>
<div className="input">
<Label htmlFor="name">{t("firebase.token_url")}</Label>
<Label htmlFor="name" className="dark:text-gray-200">{t("firebase.token_url")}</Label>
<Input
data-type="token_url"
onChange={handleChange}
@@ -97,7 +97,7 @@ export default function ConfigFirebase() {
/>
</div>
<div className="input">
<Label htmlFor="desc">{t("firebase.project_id")}</Label>
<Label htmlFor="desc" className="dark:text-gray-200">{t("firebase.project_id")}</Label>
<Input
data-type="project_id"
onChange={handleChange}
@@ -107,7 +107,7 @@ export default function ConfigFirebase() {
/>
</div>
<div className="input">
<Label htmlFor="desc">{t("firebase.private_key")}</Label>
<Label htmlFor="desc" className="dark:text-gray-200">{t("firebase.private_key")}</Label>
<Textarea
rows={10}
spellCheck={false}
@@ -119,7 +119,7 @@ export default function ConfigFirebase() {
/>
</div>
<div className="input">
<Label htmlFor="desc">{t("firebase.client_email")}</Label>
<Label htmlFor="desc" className="dark:text-gray-200">{t("firebase.client_email")}</Label>
<Input
data-type="client_email"
onChange={handleChange}
+16 -18
View File
@@ -1,12 +1,11 @@
import { ChangeEvent, FC, useState } from "react";
import { useTranslation } from "react-i18next";
import Select, { Option } from "../../../../common/component/styled/Select";
import Button from "../../../../common/component/styled/Button";
import Input from "../../../../common/component/styled/Input";
import Toggle from "../../../../common/component/styled/Toggle";
import options from "./items.json";
import Styled from "./styled";
import IconMinus from "../../../../assets/icons/minus.circle.svg";
import { useTranslation } from "react-i18next";
interface Issuer {
domain: string;
@@ -32,32 +31,31 @@ const IssuerList: FC<Props> = ({ issuers = [], onChange }) => {
(!newDomain && !select?.value) ||
!select?.title ||
issuers.some((issuer) => issuer.domain === newDomain);
return (
<Styled>
<ul className="issuers">
<div className="py-4 w-full flex flex-col gap-4">
<ul className="flex flex-col gap-4">
{issuers.map(({ enable, favicon, domain }) => {
return (
<li key={domain} className="issuer">
<div className="left">
<li key={domain} className="flex items-center justify-between gap-10">
<div className="flex-1 flex items-center justify-between">
<IconMinus
className="remove"
className="cursor-pointer"
onClick={() => {
onChange(issuers.filter((issuer) => issuer.domain !== domain));
}}
/>
<div className="data">
{Boolean(favicon) && <img src={favicon} alt="logo" className="icon" />}
<div className="flex items-center justify-between gap-4">
{Boolean(favicon) && <img src={favicon} alt="logo" className="w-8 h-8" />}
<Input
readOnly
value={domain}
prefix="https://"
placeholder="Issuer Domain"
className="url"
className="w-[280px]"
/>
</div>
</div>
<div className="right">
<div className="w-14 flex justify-end">
<Toggle
data-checked={enable}
onClick={() => {
@@ -74,8 +72,8 @@ const IssuerList: FC<Props> = ({ issuers = [], onChange }) => {
);
})}
<li className="issuer add">
<div className="left">
<li className="flex items-center justify-between gap-10 cursor-pointer">
<div className="flex-1 flex items-center justify-between">
<Select
options={options.map((option) => ({
...option,
@@ -84,18 +82,18 @@ const IssuerList: FC<Props> = ({ issuers = [], onChange }) => {
current={select}
updateSelect={setSelect}
/>
<div className="data">
<div className="flex items-center justify-between gap-4">
<Input
onChange={handleNewDomain}
readOnly={!!select?.value}
value={select?.value || newDomain}
prefix="https://"
placeholder="domain.com"
className="url"
className="w-[280px]"
/>
</div>
</div>
<div className="right">
<div className="w-14 flex justify-end">
<Button
disabled={disableBtn}
onClick={() => {
@@ -119,7 +117,7 @@ const IssuerList: FC<Props> = ({ issuers = [], onChange }) => {
</li>
</ul>
{/* <IconPlus className="add" /> */}
</Styled>
</div>
);
};
@@ -1,52 +0,0 @@
import styled from "styled-components";
const Styled = styled.div`
padding: 16px 0;
width: 100%;
display: flex;
flex-direction: column;
gap: 16px;
.issuers {
display: flex;
flex-direction: column;
gap: 16px;
.issuer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 40px;
.left {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
.remove {
cursor: pointer;
}
.data {
display: flex;
align-items: center;
gap: 16px;
justify-content: space-between;
> .icon {
width: 32px;
height: 32px;
}
> .url {
width: 280px;
}
}
}
.right {
width: 56px;
display: flex;
justify-content: flex-end;
}
}
}
.add {
cursor: pointer;
}
`;
export default Styled;
+12 -12
View File
@@ -77,9 +77,9 @@ export default function Logins() {
<div className="row">
<div className="title">
<div className="txt">
<Label>{t("password")}</Label>
<Label className="dark:text-gray-200">{t("password")}</Label>
</div>
<span className="desc">{t("password_desc")}</span>
<span className="desc dark:!text-gray-400" >{t("password_desc")}</span>
</div>
<Toggle
onClick={handleToggle.bind(null, { password: !password })}
@@ -91,9 +91,9 @@ export default function Logins() {
<div className="row">
<div className="title">
<div className="txt">
<Label>{t("magic_link")}</Label>
<Label className="dark:text-gray-200">{t("magic_link")}</Label>
</div>
<span className="desc">{t("magic_link_desc")}</span>
<span className="desc dark:!text-gray-400" >{t("magic_link_desc")}</span>
</div>
<Toggle
onClick={handleToggle.bind(null, { magic_link: !magic_link })}
@@ -105,10 +105,10 @@ export default function Logins() {
<div className="row">
<div className="title">
<div className="txt">
<Label>{t("google")}</Label>
<Label className="dark:text-gray-200">{t("google")}</Label>
<Tooltip link="https://doc.voce.chat/setting/third_login/login-google" />
</div>
<span className="desc">{t("google_desc")}</span>
<span className="desc dark:!text-gray-400" >{t("google_desc")}</span>
</div>
<Toggle
onClick={handleToggle.bind(null, { google: !google })}
@@ -128,10 +128,10 @@ export default function Logins() {
<div className="row">
<div className="title">
<div className="txt">
<Label>{t("github")}</Label>
<Label className="dark:text-gray-200">{t("github")}</Label>
<Tooltip link="https://doc.voce.chat/setting/third_login/login-github" />
</div>
<span className="desc">{t("github_desc")}</span>
<span className="desc dark:!text-gray-400" >{t("github_desc")}</span>
</div>
<Toggle
onClick={handleToggle.bind(null, { github: !github })}
@@ -159,10 +159,10 @@ export default function Logins() {
<div className="row">
<div className="title">
<div className="txt">
<Label>{t("metamask")}</Label>
<Label className="dark:text-gray-200">{t("metamask")}</Label>
<Tooltip link="https://doc.voce.chat/setting/third_login/login-metamask" />
</div>
<span className="desc">{t("metamask_desc")}</span>
<span className="desc dark:!text-gray-400" >{t("metamask_desc")}</span>
</div>
<Toggle
onClick={handleToggle.bind(null, { metamask: !metamask })}
@@ -174,10 +174,10 @@ export default function Logins() {
<div className="row">
<div className="title">
<div className="txt">
<Label htmlFor="desc">{t("oidc")}</Label>
<Label className="dark:text-gray-200" htmlFor="desc">{t("oidc")}</Label>
<Tooltip link="https://doc.voce.chat/setting/third_login/login-webid" />
</div>
<span className="desc">{t("oidc_desc")}</span>
<span className="desc dark:!text-gray-400" >{t("oidc_desc")}</span>
</div>
</div>
<div className="row">
+6 -6
View File
@@ -57,11 +57,11 @@ export default function ConfigSMTP() {
<StyledContainer>
<div className="inputs">
<div className="input row">
<Label>{t("enable")}</Label>
<Label className="dark:text-gray-200">{t("enable")}</Label>
<Toggle onClick={toggleEnable} data-checked={enabled}></Toggle>
</div>
<div className="input">
<Label htmlFor="name">{t("host")}</Label>
<Label className="dark:text-gray-200" htmlFor="name">{t("host")}</Label>
<Input
disabled={!enabled}
data-type="host"
@@ -72,7 +72,7 @@ export default function ConfigSMTP() {
/>
</div>
<div className="input">
<Label htmlFor="desc">{t("port")}</Label>
<Label className="dark:text-gray-200" htmlFor="desc">{t("port")}</Label>
<Input
disabled={!enabled}
type={"number"}
@@ -84,7 +84,7 @@ export default function ConfigSMTP() {
/>
</div>
<div className="input">
<Label htmlFor="desc">{t("from")}</Label>
<Label className="dark:text-gray-200" htmlFor="desc">{t("from")}</Label>
<Input
disabled={!enabled}
data-type="from"
@@ -95,7 +95,7 @@ export default function ConfigSMTP() {
/>
</div>
<div className="input">
<Label htmlFor="desc">{t("username")}</Label>
<Label className="dark:text-gray-200" htmlFor="desc">{t("username")}</Label>
<Input
disabled={!enabled}
data-type="username"
@@ -106,7 +106,7 @@ export default function ConfigSMTP() {
/>
</div>
<div className="input">
<Label htmlFor="desc">{t("password")}</Label>
<Label className="dark:text-gray-200" htmlFor="desc">{t("password")}</Label>
<Input
type={"password"}
disabled={!enabled}

Some files were not shown because too many files have changed in this diff Show More