refactor: more tailwind components

This commit is contained in:
Tristan Yang
2023-01-30 18:56:37 +08:00
parent 5a2ac0f4ed
commit f5aabee719
46 changed files with 185 additions and 796 deletions
+1
View File
@@ -12,6 +12,7 @@ export interface MessagePayload {
content: string;
expires_in?: number | null;
properties?: {
name?: string;
local_id?: number;
content_type: string;
size: number;
+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 12C8 13.1046 7.10457 14 6 14C4.89543 14 4 13.1046 4 12C4 10.8954 4.89543 10 6 10C7.10457 10 8 10.8954 8 12ZM14 12C14 13.1046 13.1046 14 12 14C10.8954 14 10 13.1046 10 12C10 10.8954 10.8954 10 12 10C13.1046 10 14 10.8954 14 12ZM18 14C19.1046 14 20 13.1046 20 12C20 10.8954 19.1046 10 18 10C16.8954 10 16 10.8954 16 12C16 13.1046 16.8954 14 18 14Z" fill="#616161"/>
<svg width="24" height="24" viewBox="0 0 24 24" fill="#616161" xmlns="http://www.w3.org/2000/svg">
<path d="M8 12C8 13.1046 7.10457 14 6 14C4.89543 14 4 13.1046 4 12C4 10.8954 4.89543 10 6 10C7.10457 10 8 10.8954 8 12ZM14 12C14 13.1046 13.1046 14 12 14C10.8954 14 10 13.1046 10 12C10 10.8954 10.8954 10 12 10C13.1046 10 14 10.8954 14 12ZM18 14C19.1046 14 20 13.1046 20 12C20 10.8954 19.1046 10 18 10C16.8954 10 16 10.8954 16 12C16 13.1046 16.8954 14 18 14Z" />
</svg>

Before

Width:  |  Height:  |  Size: 479 B

After

Width:  |  Height:  |  Size: 468 B

+27 -2
View File
@@ -1,12 +1,37 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.flex-center {
@apply flex items-center justify-center;
}
}
@layer utilities {
.checkbox {
appearance: none;
margin: 0;
}
.checkbox::before {
content: "";
display: block;
width: 10px;
height: 10px;
margin: 4px;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
transform: scale(0);
transform-origin: bottom left;
transition: 120ms transform ease-in-out;
box-shadow: inset 10px 10px #1fe1f9;
}
.checkbox:checked::before {
transform: scale(1);
}
}
/* 全局设置滚动条 */
::-webkit-scrollbar-thumb {
background: #ddd;
border-radius: 4px;
}
::-webkit-scrollbar {
background: transparent;
width: 5px;
+13 -43
View File
@@ -1,5 +1,4 @@
import { useState } from "react";
import styled from "styled-components";
import { hideAll } from "tippy.js";
import { useAppSelector } from "../../app/store";
import IconInvite from "../../assets/icons/add.person.svg";
@@ -10,38 +9,6 @@ import UsersModal from "./UsersModal";
import InviteModal from "./InviteModal";
import { useTranslation } from "react-i18next";
const Styled = styled.ul`
z-index: 999;
user-select: none;
box-shadow: 0 24px 48px -12px rgba(16, 24, 40, 0.18);
border-radius: 12px;
color: #616161;
background: #fff;
display: flex;
flex-direction: column;
padding: 4px;
.item {
border-radius: 3px;
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
font-size: 14px;
line-height: 20px;
cursor: pointer;
padding: 10px 8px;
&:hover {
background: rgba(116, 127, 141, 0.2);
}
.icon {
width: 20px;
height: 20px;
path {
fill: #475467;
}
}
}
`;
export default function AddEntriesMenu() {
const { t } = useTranslation();
const currentUser = useAppSelector((store) => store.authData.user);
@@ -74,29 +41,32 @@ export default function AddEntriesMenu() {
const handleCloseModal = () => {
setChannelModalVisible(false);
};
const itemClass = "rounded flex items-center gap-2 text-sm font-semibold cursor-pointer px-2 py-2.5 hover:bg-gray-800/20";
const iconClass = "w-5 h-5";
return (
<>
<Styled>
<ul className="flex flex-col rounded-xl drop-shadow p-1 select-none text-gray-500 bg-white">
{/* temp remove public channel */}
{currentUser?.is_admin && (
<li className="item" onClick={handleOpenChannelModal.bind(null, false)}>
<ChannelIcon className="icon" />
<li className={itemClass} onClick={handleOpenChannelModal.bind(null, false)}>
<ChannelIcon className={iconClass} />
{t("action.new_channel")}
</li>
)}
<li className="item" onClick={handleOpenChannelModal.bind(null, true)}>
<ChannelIcon personal={true} className="icon" />
<li className={itemClass} onClick={handleOpenChannelModal.bind(null, true)}>
<ChannelIcon personal={true} className={iconClass} />
{t("action.new_private_channel")}
</li>
<li className="item" onClick={toggleUsersModalVisible}>
<IconMention className="icon" />
<li className={itemClass} onClick={toggleUsersModalVisible}>
<IconMention className={iconClass} />
{t("action.new_msg")}
</li>
<li className="item" onClick={toggleInviteModalVisible}>
<IconInvite className="icon" />
<li className={itemClass} onClick={toggleInviteModalVisible}>
<IconInvite className={iconClass} />
{t("action.invite_people")}
</li>
</Styled>
</ul>
{channelModalVisible && <ChannelModal personal={isPrivate} closeModal={handleCloseModal} />}
{usersModalVisible && <UsersModal closeModal={toggleUsersModalVisible} />}
{inviteModalVisible && <InviteModal closeModal={toggleInviteModalVisible} />}
+1 -1
View File
@@ -34,7 +34,7 @@ const Avatar: FC<Props> = ({
} else {
return (
<div
className="rounded-full flex items-center justify-center"
className="rounded-full flex-center"
style={{
width,
height,
+1 -1
View File
@@ -14,7 +14,7 @@ interface Props {
type?: "chat" | "user";
}
const classes = {
box: "w-[200px] h-[200px] cursor-pointer bg-[#f9fafb] rounded-3xl flex flex-col justify-center items-center gap-4",
box: "w-[200px] h-[200px] cursor-pointer bg-[#f9fafb] rounded-3xl flex-center flex-col gap-4",
boxIcon: "w-10 h-10",
boxTip: "px-5 text-sm text-[#475467] font-bold text-center"
};
+2 -9
View File
@@ -1,14 +1,7 @@
import { FC } from "react";
import styled from "styled-components";
import HashIcon from "../../assets/icons/channel.svg";
import LockHashIcon from "../../assets/icons/channel.private.svg";
const Styled = styled.div`
display: flex;
&.muted path {
fill: #d0d5dd;
}
`;
interface Props {
personal?: boolean;
muted?: boolean;
@@ -17,9 +10,9 @@ interface Props {
const ChannelIcon: FC<Props> = ({ personal = false, muted = false, className = "" }) => {
return (
<Styled className={`${muted ? "muted" : ""} ${className}`}>
<div className={`flex${muted ? "!text-gray-400 !fill-slate-400" : ""} ${className}`}>
{personal ? <LockHashIcon /> : <HashIcon />}
</Styled>
</div>
);
};
@@ -14,7 +14,7 @@ const Audio: FC<Props> = ({ url }) => {
if (!url) return null;
return (
<div className="flex items-center justify-center h-full">
<div className="flex-center h-full">
{err ? (
<div className="p-[18px] text-base text-gray-500">Unable to play this audio</div>
) : (
@@ -34,8 +34,6 @@ const StyledSocialButton = styled(Button)`
}
}
> .hide {
/* z-index: 1; */
/* opacity: 0; */
left: 0;
top: 0;
position: absolute;
+5 -52
View File
@@ -1,56 +1,8 @@
import { useRef, useState, useEffect, FC } from "react";
import styled, { keyframes } from "styled-components";
import { useOutsideClick, useKey } from "rooks";
import { Ring } from "@uiball/loaders";
import Modal from "./Modal";
const AniFadeIn = keyframes`
from {
background: transparent;
}
to {
background: rgba(1, 1, 1, 0.9);
}
`;
const StyledWrapper = styled.div`
transition: all 0.5s ease;
width: 100vw;
height: 100vh;
animation: ${AniFadeIn} 0.3s ease-in-out forwards;
display: flex;
align-items: center;
justify-content: center;
.box {
position: relative;
transition: all 0.2s ease;
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 10px;
> .loading {
position: absolute;
top: 40%;
left: 50%;
transform: translateX(-50%);
}
> .image {
overflow: auto;
img {
max-width: 70vw;
max-height: 80vh;
}
}
&.loading .image img {
filter: blur(2px);
}
}
`;
export interface PreviewImageData {
originUrl: string;
@@ -100,10 +52,11 @@ const ImagePreviewModal: FC<Props> = ({ download = true, data, closeModal }) =>
const { originUrl, downloadLink, name, type } = data;
return (
<Modal>
<StyledWrapper>
<div className={`box ${loading ? "loading" : ""}`} ref={wrapperRef}>
<div className="image">
<div className="w-screen h-screen flex-center bg-black/90">
<div className={`relative flex flex-col justify-start gap-3 ${loading ? "absolute top-1/3 left-1/2 -translate-x-1/2" : ""}`} ref={wrapperRef}>
<div className="overflow-auto">
<img
className={`max-w-[70vw] max-h-[80vh] ${loading ? "blur-sm" : ""}`}
src={url}
alt="preview"
/>
@@ -126,7 +79,7 @@ const ImagePreviewModal: FC<Props> = ({ download = true, data, closeModal }) =>
</div>
)}
</div>
</StyledWrapper>
</div>
</Modal>
);
};
+1 -1
View File
@@ -23,7 +23,7 @@ const InactiveScreen: FC<Props> = () => {
location.reload();
};
return (
<div className="w-screen h-screen flex justify-center items-center text-4xl font-bold">
<div className="w-screen h-screen flex-center text-4xl font-bold">
<div className="flex flex-col items-center gap-2">
<h1 className="text-4xl font-bold">{t("inactive.title")}</h1>
<p className="text-gray-400 text-base font-semibold" >{t("inactive.desc")}</p>
@@ -1,6 +1,5 @@
import { useEffect, useState, FC } from "react";
import toast from "react-hot-toast";
import styled from "styled-components";
import { useNavigate } from "react-router-dom";
import Modal from "../Modal";
import useLeaveChannel from "../../hook/useLeaveChannel";
@@ -8,29 +7,7 @@ import StyledModal from "../styled/Modal";
import Button from "../styled/Button";
import User from "../User";
import { useTranslation } from "react-i18next";
const UserList = styled.ul`
display: flex;
flex-direction: column;
max-height: 260px;
padding: 16px 0;
overflow-y: scroll;
.user {
cursor: pointer;
display: flex;
align-items: center;
padding: 0 8px;
width: -webkit-fill-available;
&:hover,
&.selected {
background: rgba(116, 127, 141, 0.1);
}
> a {
width: 100%;
}
}
`;
import clsx from "clsx";
interface Props {
id: number;
closeModal: () => void;
@@ -90,19 +67,19 @@ const TransferOwnerModal: FC<Props> = ({ id, closeModal, withLeave = true }) =>
</>
}
>
<UserList>
<ul className="flex flex-col max-h-[260px] py-4 overflow-y-scroll">
{otherMembers.map((id) => {
return (
<li
key={id}
className={`user ${uid == id ? "selected" : ""}`}
className={clsx(`cursor-pointer flex items-center px-2 hover:bg-gray-500/10`, uid == id ? "bg-gray-500/10" : "")}
onClick={handleSelectUser.bind(null, id)}
>
<User uid={id} interactive={false} />
</li>
);
})}
</UserList>
</ul>
</StyledModal>
</Modal>
);
+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 flex-col items-center justify-center gap-4", fullscreen ? "w-screen h-screen" : "")}>
<div className={clsx("w-full h-full flex-center flex-col gap-4", fullscreen ? "w-screen h-screen" : "")}>
<Ring size={40} lineWeight={5} speed={2} color="black" />
<Button className={clsx(`danger`, reloadVisible ? "visible" : "invisible")} onClick={handleReload}>
Reload
@@ -1,16 +1,9 @@
import { FC, ReactElement, useEffect, useState } from "react";
import styled from "styled-components";
import StyledMsg from "./styled";
import renderContent from "./renderContent";
import Avatar from "../Avatar";
import useFavMessage from "../../hook/useFavMessage";
const StyledFav = styled.div`
display: flex;
flex-direction: column;
border-radius: var(--br);
background-color: #f4f4f5;
`;
type Props = {
id?: string;
};
@@ -25,7 +18,7 @@ const FavoredMessage: FC<Props> = ({ id = "" }) => {
const favorite_mids = messages.map(({ from_mid }) => +from_mid) || [];
setMsgs(
<StyledFav data-favorite-mids={favorite_mids.join(",")} className="favorite">
<div data-favorite-mids={favorite_mids.join(",")} className="favorite flex flex-col rounded-md bg-slate-200">
<div className="list">
{messages.map((msg, idx) => {
const { user = {}, download, content, content_type, properties, thumbnail } = msg;
@@ -54,7 +47,7 @@ const FavoredMessage: FC<Props> = ({ id = "" }) => {
);
})}
</div>
</StyledFav>
</div>
);
}, [favorites, id]);
+3 -2
View File
@@ -7,6 +7,7 @@ import LinkifyText from '../LinkifyText';
import Avatar from "../Avatar";
import { useAppSelector } from "../../../app/store";
import { MessagePayload } from "../../../app/slices/message";
const Styled = styled.div`
display: flex;
align-items: flex-start;
@@ -96,7 +97,7 @@ const Styled = styled.div`
}
`;
const renderContent = (data) => {
const renderContent = (data: MessagePayload) => {
const { content_type, content, thumbnail, properties } = data;
let res = null;
switch (content_type) {
@@ -116,7 +117,7 @@ const renderContent = (data) => {
break;
case ContentTypes.file:
{
const { content_type, name, size } = properties;
const { content_type = "", name, size } = properties || {};
const icon = getFileIcon(content_type, name);
if (isImage(content_type, size)) {
res = <img className="pic" src={thumbnail} />;
+19 -103
View File
@@ -1,95 +1,6 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
import { useLazyGetOGInfoQuery } from "../../../app/services/message";
const StyledCompact = styled.a`
background: #f3f4f6;
border: 1px solid #d4d4d4;
box-sizing: border-box;
border-radius: 6px;
padding: 12px 8px;
display: flex;
align-items: center;
gap: 8px;
width: 380px;
.favicon {
display: flex;
width: 48px;
height: 48px;
border-radius: 4px;
img {
object-fit: contain;
}
}
.info {
display: flex;
flex-direction: column;
.title {
font-style: normal;
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #1c1c1e;
}
.desc {
font-weight: 500;
font-size: 12px;
line-height: 18px;
color: #616161;
}
.link {
font-weight: 400;
font-size: 10px;
line-height: 18px;
color: #616161;
}
.dots {
width: 288px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
`;
const StyledDetails = styled.a`
width: 380px;
padding: 12px;
background: #f3f4f6;
border: 1px solid #d4d4d4;
box-sizing: border-box;
border-radius: 6px;
display: flex;
flex-direction: column;
.title {
font-weight: 600;
font-size: 16px;
line-height: 24px;
color: #06aed4;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.desc {
font-weight: 400;
font-size: 12px;
line-height: 18px;
color: #616161;
margin-bottom: 8px;
width: 356px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.image {
width: 100%;
height: 180px;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
}
`;
export default function URLPreview({ url = "" }) {
const [favicon, setFavicon] = useState("");
@@ -122,26 +33,31 @@ export default function URLPreview({ url = "" }) {
};
if (!url || !data || !data.title) return null;
const { title, description, ogImage } = data;
const containerClass = `flex items-center border border-solid border-gray-300 box-border rounded-md w-[380px]`;
const dotsClass = `overflow-hidden whitespace-nowrap text-ellipsis`;
return ogImage ? (
<StyledDetails href={url} target="_blank">
<h3 className="title">{title}</h3>
<p className="desc dots">{description}</p>
<div className="image">
<img src={ogImage} alt="og image" />
<a className={`${containerClass} flex-col !items-start p-3`} href={url} target="_blank" rel="noreferrer">
<h3 className={`text-base text-primary-500 w-full ${dotsClass}`}>{title}</h3>
<p className={`text-xs text-gray-400 mb-2 w-full ${dotsClass}`}>{description}</p>
<div className="w-full h-[180px]">
<img className="w-full h-full object-cover" src={ogImage} alt="og image" />
</div>
</StyledDetails>
</a>
) : (
<StyledCompact href={url} target="_blank">
<a
className={`${containerClass} gap-2 px-2 py-3`}
href={url} target="_blank" rel="noreferrer">
{favicon && (
<div className="favicon">
<img onError={handleFavError} src={favicon} alt="favicon" />
<div className="flex w-12 h-12 rounded">
<img onError={handleFavError} className="object-contain" src={favicon} alt="favicon" />
</div>
)}
<div className="info">
<h3 className="title">{title}</h3>
<p className="desc dots">{description}</p>
<span className="link dots">{url}</span>
<div className="flex flex-col">
<h3 className="text-sm text-gray-900">{title}</h3>
<p className={`text-xs text-gray-500 w-[288px] ${dotsClass}`}>{description}</p>
<span className={`text-[10px] text-gray-500 w-[288px] ${dotsClass}`}>{url}</span>
</div>
</StyledCompact>
</a>
);
}
+13 -11
View File
@@ -4,11 +4,11 @@ import Tippy from "@tippyjs/react";
import IconMessage from "../../../assets/icons/message.svg";
import IconMore from "../../../assets/icons/more.svg";
import Avatar from "../Avatar";
import StyledWrapper from "./styled";
import StyledMenu from "../styled/Menu";
import useUserOperation from "../../hook/useUserOperation";
import { useAppSelector } from "../../../app/store";
import { useTranslation } from "react-i18next";
import clsx from "clsx";
interface Props {
uid: number;
@@ -42,11 +42,13 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
avatar
// introduction = "This guy has nothing to introduce",
} = data;
const canRemoveFromServer = type == "embed" && canRemove;
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");
return (
<StyledWrapper className={type}>
<div className={containerClass}>
<Avatar
width={80}
height={80}
@@ -57,11 +59,11 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
<h2 className="text-lg select-text font-bold text-[#1c1c1e]">{name}</h2>
<span className="text-sm text-[#98a2b3] select-text">{email}</span>
{/* <p className="intro">{introduction}</p> */}
<ul className="icons">
<ul className={clsx("mt-6 flex items-center gap-2", isCard && "pb-0.5")}>
<NavLink to={`/chat/dm/${uid}`}>
<li className="icon chat">
<li className={`${iconClass} icon chat`}>
<IconMessage />
<span className="txt">{t("send_msg")}</span>
<span>{t("send_msg")}</span>
</li>
</NavLink>
{/* <NavLink to={`#`}> */}
@@ -93,13 +95,13 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
</StyledMenu>
}
>
<li className={`icon more ${hasMore ? "" : "disabled"}`}>
<IconMore />
<span className="txt">{ct("more")}</span>
<li className={`${iconClass} icon ${hasMore ? "" : "text-gray-500"}`}>
<IconMore className={hasMore ? "fill-primary-500" : ""} />
<span >{ct("more")}</span>
</li>
</Tippy>
</ul>
</StyledWrapper>
</div>
);
};
-64
View File
@@ -1,64 +0,0 @@
import styled from "styled-components";
const StyledWrapper = styled.div`
z-index: 998;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin-top: 80px;
width: 432px;
gap: 4px;
.icons {
margin-top: 24px;
display: flex;
align-items: center;
gap: 8px;
.icon {
cursor: pointer;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #22ccee;
display: flex;
align-items: center;
flex-direction: column;
gap: 4px;
background: #f9fafb;
border-radius: 8px;
width: 128px;
padding: 14px 0 12px 0;
&:not(.disabled):hover {
background: #f2f4f7;
}
&.call,
&.more {
svg path {
fill: #22ccee;
}
}
&.disabled {
color: #667085;
svg path {
fill: #667085;
}
}
.txt {
user-select: none;
}
}
}
&.card {
padding: 16px;
width: 280px;
background: #ffffff;
box-shadow: 0px 20px 25px 20px rgba(31, 41, 55, 0.1), 0px 10px 10px rgba(31, 41, 55, 0.04);
border-radius: 6px;
.icons {
padding-bottom: 2px;
}
}
`;
export default StyledWrapper;
+11 -55
View File
@@ -1,53 +1,8 @@
// import clsx from "clsx";
import { FC, MouseEvent } from "react";
import { useTranslation } from "react-i18next";
import styled from "styled-components";
import Button from '../component/styled/Button';
const StyledWrapper = styled.div`
width: 100%;
position: absolute;
bottom: 64px;
left: 0;
padding: 8px;
display: flex;
align-items: center;
justify-content: space-between;
color: #333;
background: #fff;
border: 1px solid #e5e7eb;
box-shadow: 0 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06);
border-radius: 25px;
.txt {
padding: 8px;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
}
.btns {
display: flex;
align-items: center;
gap: 14px;
.btn {
color: #fff;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 20px;
padding: 8px 14px;
background: #1fe1f9;
border: 1px solid #1fe1f9;
box-sizing: border-box;
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 25px;
&.reset {
background: none;
color: #667085;
border: none;
box-shadow: none;
}
}
}
`;
interface Props {
saveHandler: (e: MouseEvent) => void;
@@ -56,18 +11,19 @@ interface Props {
const SaveTip: FC<Props> = ({ saveHandler, resetHandler }) => {
const { t } = useTranslation("setting");
// const btnClass=clsx("")
return (
<StyledWrapper className="animate__animated animate__flipInX animate__faster">
<span className="txt">{t('save_tip')}</span>
<div className="btns">
<button className="btn reset" onClick={resetHandler}>
<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="flex items-center gap-3">
<Button className="small ghost !border-none !text-gray-500 !shadow-none" onClick={resetHandler}>
{t('reset')}
</button>
<button className="btn" onClick={saveHandler}>
</Button>
<Button className="small !rounded-full" onClick={saveHandler}>
{t('save_change')}
</button>
</Button>
</div>
</StyledWrapper>
</div>
);
};
+5 -29
View File
@@ -1,34 +1,10 @@
import { useState, useRef, useEffect } from "react";
import styled from "styled-components";
import { useOutsideClick } from "rooks";
import Tooltip from "../Tooltip";
import { Picker } from 'emoji-mart';
import SmileIcon from "../../../assets/icons/emoji.smile.svg";
import clsx from "clsx";
const Styled = styled.div`
position: relative;
width: fit-content;
display: flex;
align-items: center;
> .emoji {
cursor: pointer;
user-select: none;
}
> svg {
width: 22px;
height: 22px;
}
> .picker {
display: none;
position: absolute;
top: -20px;
left: -20px;
transform: translateY(-100%);
&.visible {
display: block;
}
}
`;
export default function EmojiPicker({ selectEmoji }: { selectEmoji: (e: string) => void }) {
const ref = useRef<HTMLDivElement>(null);
@@ -75,12 +51,12 @@ export default function EmojiPicker({ selectEmoji }: { selectEmoji: (e: string)
return (
<Tooltip placement="top" tip="Emojis" disabled={visible}>
<Styled>
<div ref={ref} className={`picker ${visible ? "visible" : ""}`}>
<div className="relative w-fit flex items-center">
<div ref={ref} className={clsx(`absolute -top-5 -left-5 -translate-y-full`, visible ? 'block' : 'hidden')}>
{/* emoji picker */}
</div>
<SmileIcon data-emoji="toggler" className="emoji" onClick={togglePickerVisible} />
</Styled>
<SmileIcon data-emoji="toggler" className="cursor-pointer select-none !w-[22px] !h-[22px]" onClick={togglePickerVisible} />
</div>
</Tooltip>
);
}
+2 -2
View File
@@ -96,7 +96,7 @@ const renderContent = (data: MessagePayload) => {
break;
case ContentTypes.file:
{
const { content_type, name, size } = properties;
const { content_type = "", name, size } = properties || {};
const image = isImage(content_type, size);
// console.log("replying data", content_type, size, image);
if (image) {
@@ -136,7 +136,7 @@ export default function Replying({
removeReplying();
};
if (!msg) return null;
const { from_uid } = msg;
const { from_uid = 0 } = msg;
const user = usersData[from_uid];
return (
<Styled className="reply">
-13
View File
@@ -1,13 +0,0 @@
import { useState } from "react";
export default function useFiles(initialFiles = []) {
const [files, setFiles] = useState(initialFiles);
const resetFiles = () => {
setFiles([]);
};
return {
files,
setFiles,
resetFiles
};
}
+8 -52
View File
@@ -1,56 +1,11 @@
import { ChangeEvent, useRef, FC } from "react";
import styled from "styled-components";
import { NavLink } from "react-router-dom";
import { useOutsideClick } from "rooks";
import useFilteredUsers from "../hook/useFilteredUsers";
import User from "./User";
import Modal from "./Modal";
import { useTranslation } from "react-i18next";
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
width: 440px;
max-height: 402px;
background: #fff;
box-shadow: 0 25px 50px rgba(31, 41, 55, 0.25);
border-radius: 8px;
transition: all 0.5s ease;
.search {
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
padding: 8px;
width: -webkit-fill-available;
input {
outline: none;
width: -webkit-fill-available;
padding: 8px;
font-size: 14px;
line-height: 20px;
border: none;
}
}
.users {
display: flex;
flex-direction: column;
height: 260px;
padding: 16px 0;
overflow-y: scroll;
.user {
cursor: pointer;
display: flex;
align-items: center;
padding: 0 8px;
width: -webkit-fill-available;
&:hover {
background: rgba(116, 127, 141, 0.1);
}
> a {
width: 100%;
}
}
}
`;
interface Props {
closeModal: () => void;
@@ -66,19 +21,20 @@ const UsersModal: FC<Props> = ({ closeModal }) => {
updateInput(evt.target.value);
};
return (
<Modal>
<StyledWrapper ref={wrapperRef}>
<div className="search">
<input value={input} onChange={handleSearch} placeholder={t("search_user_placeholder")} />
<div className="flex flex-col w-[440px] max-h-[402px] bg-white drop-shadow rounded-lg" ref={wrapperRef}>
<div className="shadow-md p-2">
<input className=" p-2 text-sm" value={input} onChange={handleSearch} placeholder={t("search_user_placeholder")} />
</div>
{users && (
<ul className="users">
<ul className="flex flex-col overflow-y-scroll h-[260px] py-4">
{users.map((u) => {
const { uid = 0 } = u || {};
return (
<li key={uid} className="user">
<NavLink onClick={closeModal} to={`/chat/dm/${uid}`}>
<li key={uid} className="cursor-pointer px-2 hover:bg-gray-600/10">
<NavLink className={'w-full'} onClick={closeModal} to={`/chat/dm/${uid}`}>
<User uid={uid} interactive={false} />
</NavLink>
</li>
@@ -86,7 +42,7 @@ const UsersModal: FC<Props> = ({ closeModal }) => {
})}
</ul>
)}
</StyledWrapper>
</div>
</Modal>
);
};
+7 -36
View File
@@ -1,40 +1,11 @@
import { InputHTMLAttributes } from "react";
import styled from "styled-components";
const Styled = styled.input`
-webkit-appearance: none;
/* Remove most all native input styles */
appearance: none;
/* Not removed via appearance */
margin: 0;
width: 20px;
height: 20px;
border: 1px solid #d0d5dd;
border-radius: 6px;
place-content: center;
&::before {
content: "";
display: block;
width: 10px;
height: 10px;
margin: 4px;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
transform: scale(0);
transform-origin: bottom left;
transition: 120ms transform ease-in-out;
box-shadow: inset 10px 10px #1fe1f9;
}
&:checked {
border-color: #1fe1f9;
&:before {
transform: scale(1);
}
}
&:disabled {
opacity: 0.4;
}
`;
export default function StyledCheckbox(props: InputHTMLAttributes<HTMLInputElement>) {
return <Styled readOnly {...props} type="checkbox" />;
const { className: cbClasses } = props;
return <input
readOnly
{...props}
type="checkbox"
className={`checkbox w-5 h-5 rounded-md border border-solid border-slate-300 checked:border-[#1fe1f9] disabled:opacity-40 ${cbClasses}`}
/>;
}
+4 -25
View File
@@ -1,31 +1,10 @@
import { FC, useState } from "react";
import styled from "styled-components";
import Tippy from "@tippyjs/react";
import IconSelect from "../../../assets/icons/check.sign.svg";
import IconArrow from "../../../assets/icons/arrow.down.svg";
import Menu from "./Menu";
import { useTranslation } from "react-i18next";
const Styled = styled.div`
user-select: none;
border: 1px solid #e5e7eb;
border-radius: 4px;
padding: 8px;
display: flex;
align-items: center;
gap: 8px;
.txt {
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: #475467;
min-width: 76px;
}
> .icon {
width: 20px !important;
height: 20px !important;
}
`;
export interface Option {
icon?: string;
@@ -80,10 +59,10 @@ const Select: FC<Props> = ({ options = [], updateSelect = null, current = null }
</Menu>
}
>
<Styled onClick={toggleVisible}>
<span className="txt">{(current !== null ? current : curr)?.title || t("action.select")}</span>
<IconArrow className="icon" />
</Styled>
<div className="select-none border border-solid border-slate-200 p-2 flex items-center gap-2" onClick={toggleVisible}>
<span className="text-sm text-gray-500 min-w-[76px]">{(current !== null ? current : curr)?.title || t("action.select")}</span>
<IconArrow className="!w-5 !h-5" />
</div>
</Tippy>
);
};
+1 -1
View File
@@ -53,7 +53,7 @@ const GithubCallback: FC<Props> = ({ code, from = "webapp" }) => {
}
}, [error]);
if (error) return <span>Something Error</span>;
return <section className='flex flex-col gap-3 justify-center items-center'>
return <section className='flex-center flex-col gap-3'>
{isSuccess && from == 'widget' && <h1>Please close this window and return widget window</h1>}
<span className='text-3xl text-green-600 font-bold'>{isLoading ? "GitHub Logging in..." : "GitHub Login Success!"}</span>
</section>;
+1 -1
View File
@@ -2,7 +2,7 @@ import { DOMAttributes, ReactNode } from "react";
const StyledWrapper = ({ children }: DOMAttributes<HTMLDivElement> & { children?: ReactNode }) => {
return <div className="flex w-screen h-screen items-center justify-center break-words leading-normal">
return <div className="flex-center w-screen h-screen break-words leading-normal">
{children}
</div>;
};
+1 -1
View File
@@ -43,7 +43,7 @@ const FavList: FC<Props> = ({ cid = null, uid = null }) => {
<li key={id} className="relative border border-solid border-slate-200 rounded-md group">
<FavoredMessage id={id} />
<div className="flex items-center absolute top-2 right-2 border border-solid border-gray-300 rounded-md overflow-hidden invisible group-hover:visible">
<button className="flex justify-center items-center w-6 h-6 p-1" data-id={id} onClick={handleRemove}>
<button className="flex-center w-6 h-6 p-1" data-id={id} onClick={handleRemove}>
<IconRemove className="fill-slate-900" />
</button>
</div>
+1 -1
View File
@@ -10,7 +10,7 @@ const DnDTip = ({ context, name }: Props) => {
return (
<div
className={`flex justify-center items-center absolute left-0 top-0 w-full h-full bg-black/50`}
className={`flex-center absolute left-0 top-0 w-full h-full bg-black/50`}
>
<div className={`p-4 drop-shadow-md rounded-lg bg-primary-300`}>
<div className="p-4 pt-16 border-2 border-dashed border-[#a5f3fc] rounded-md flex flex-col items-center text-white">
+1 -1
View File
@@ -58,7 +58,7 @@ const Operations: FC<Props> = ({ context, id }) => {
// const
return (
<>
<div className="relative p-4 flex items-center justify-center gap-8 shadow-md">
<div className="relative p-4 flex-center gap-8 shadow-md">
<button className="p-2 bg-slate-100 rounded hover:bg-slate-300" onClick={toggleForwardModal}>
<IconForward />
</button>
+1 -1
View File
@@ -14,7 +14,7 @@ const LoadMore: FC<Props> = ({ pullUp = null, pulling }) => {
}
}, [inView, pullUp, pulling]);
return (
<div data-load-more className="mt-2 flex justify-center items-center w-full py-2" ref={myRef}>
<div data-load-more className="mt-2 flex-center w-full py-2" ref={myRef}>
<Waveform size={18} lineWeight={4} speed={1} color="#ccc" />
</div>
);
+1 -1
View File
@@ -46,7 +46,7 @@ function ChatPage() {
)}
{usersModalVisible && <UsersModal closeModal={toggleUsersModalVisible} />}
<StyledWrapper className={isGuest ? "guest" : ""}>
<div className="left">
<div className="left hidden md:flex">
<Server readonly={isGuest} />
{isGuest ? <GuestSessionList /> : <SessionList tempSession={tmpSession} />}
</div>
+3 -5
View File
@@ -9,7 +9,7 @@ const StyledWrapper = styled.div`
> .left {
background-color: #fff;
position: relative;
display: flex;
/* display: flex; */
flex-direction: column;
min-width: 268px;
box-shadow: inset -1px 0px 0px rgba(0, 0, 0, 0.05);
@@ -65,10 +65,6 @@ const StyledWrapper = styled.div`
&.active {
background: rgba(116, 127, 141, 0.1);
}
.avatar {
/* todo */
}
.details {
display: flex;
flex-direction: column;
@@ -149,6 +145,7 @@ const StyledWrapper = styled.div`
}
}
}
> .right {
border-radius: 0 16px 16px 0;
width: 100%;
@@ -160,6 +157,7 @@ const StyledWrapper = styled.div`
justify-content: center;
}
}
`;
export default StyledWrapper;
+1 -1
View File
@@ -156,7 +156,7 @@ function FavsPage() {
</h4>
<div className="relative group">
<FavoredMessage key={id} id={id} />
<button className="absolute top-2 right-2 flex justify-center items-center w-6 h-6 p-1 border border-solid border-slate-200 rounded invisible group-hover:visible" data-id={id} onClick={handleRemove} >
<button className="absolute top-2 right-2 flex-center w-6 h-6 p-1 border border-solid border-slate-200 rounded invisible group-hover:visible" data-id={id} onClick={handleRemove} >
<IconRemove className="fill-slate-900" />
</button>
</div>
+2 -4
View File
@@ -1,7 +1,5 @@
import { useState, useEffect } from "react";
import { useDispatch } from "react-redux";
import StyledWrapper from "./styled";
import { useNavigate, useParams } from "react-router-dom";
import { useLoginMutation } from "../../app/services/auth";
import toast from "react-hot-toast";
@@ -45,9 +43,9 @@ export default function OAuthPage() {
}, [isSuccess, data]);
return (
<StyledWrapper>
<div className="flex-center h-screen">
{loading ? "loading" : ""}
{error}
</StyledWrapper>
</div>
);
}
-76
View File
@@ -1,76 +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;
/* border: 1px solid #eee; */
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;
}
.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: 0 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: 0 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
&.google {
color: #344054;
border-color: #d0d5dd;
background: none;
}
}
}
`;
export default StyledWrapper;
+7 -8
View File
@@ -9,8 +9,8 @@ import WhoCanSignUp from "./steps/who-can-sign-up";
import InviteLink from "./steps/invite-link";
import DonePage from "./steps/done-page";
import steps from "./steps";
import StyledOnboardingPage from "./styled";
import { useTranslation } from "react-i18next";
import clsx from "clsx";
const Navigator = () => {
@@ -19,12 +19,11 @@ const Navigator = () => {
console.log("active step", activeStep);
return (
<div className="navigator">
<div className="absolute top-5 w-full flex justify-center gap-2 z-10">
{steps.map((stepToRender, indexToRender) => {
const clickable = canJumpTo.includes(stepToRender.name);
const nodeCls = `node ${indexToRender === activeStep ? "emphasized" : ""} ${indexToRender > activeStep ? "disabled" : ""
} ${clickable ? "clickable" : ""}`;
const arrowCls = `arrow ${indexToRender >= activeStep ? "disabled" : ""}`;
const itemClass = clsx(`text-sm text-gray-600`, clickable && "cursor-pointer hover:text-gray-500", indexToRender === activeStep && "font-bold text-black", indexToRender >= activeStep && "text-gray-400");
const nodeCls = `${itemClass}`;
return (
<React.Fragment key={indexToRender}>
<span
@@ -37,7 +36,7 @@ const Navigator = () => {
>
{stepToRender.label}
</span>
{indexToRender !== steps.length - 1 && <span className={arrowCls}></span>}
{indexToRender !== steps.length - 1 && <span className={nodeCls}></span>}
</React.Fragment>
);
})}
@@ -53,7 +52,7 @@ export default function OnboardingPage() {
<Helmet>
<title>{t("onboarding.title")}</title>
</Helmet>
<StyledOnboardingPage>
<div className="h-screen overflow-y-auto">
<Wizard header={<Navigator />}>
<WelcomePage />
<ServerName serverName={serverName} setServerName={setServerName} />
@@ -63,7 +62,7 @@ export default function OnboardingPage() {
<InviteLink />
<DonePage serverName={serverName} />
</Wizard>
</StyledOnboardingPage>
</div>
</>
);
}
+1 -1
View File
@@ -14,7 +14,7 @@ export default function InviteLink() {
const { link, linkCopied, copyLink, generateNewLink } = useInviteLink();
return (
<div className="h-full flex flex-col items-center justify-center relative">
<div className="h-full flex-center flex-col relative">
<span className="text-2xl mb-2 font-bold">{t("invite_title")}</span>
<span className="text-sm mb-10 text-gray-400">{t("last_tip")}</span>
<span className="text-sm text-gray-500 mb-2 font-semibold">{t("last_desc")}</span>
-43
View File
@@ -1,43 +0,0 @@
import styled from "styled-components";
const StyledOnboardingPage = styled.div`
height: 100vh;
overflow-y: auto;
> .navigator {
position: absolute;
top: 20px;
width: 100%;
display: flex;
justify-content: center;
gap: 8px;
z-index: 10;
> .node,
> .arrow {
font-weight: 400;
font-size: 14px;
line-height: 28px;
color: #101828;
cursor: default;
transition: all 150ms ease-in-out;
&.disabled {
color: #d0d5dd;
}
&.emphasized {
font-weight: 600;
}
&.clickable {
cursor: pointer;
&:hover {
color: #717180;
}
}
}
}
`;
export default StyledOnboardingPage;
+5 -7
View File
@@ -32,14 +32,12 @@ const Index = () => {
}, [getUrlSuccess, data]);
// if(!fetch)
return (
<div className="setting">
<p className="label">{t("overview.url.title")}</p>
<p className="tip">
<span className="txt">
{t("overview.url.desc")}
</span>
<div className="text-sm">
<p className="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-4">
<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>
+9 -74
View File
@@ -1,5 +1,4 @@
// import { useState, useEffect, ChangeEvent } from "react";
import styled from "styled-components";
import { useTranslation } from "react-i18next";
import StyledRadio from "../../../common/component/styled/Radio";
import { useAppSelector } from "../../../app/store";
@@ -10,77 +9,12 @@ import Language from './Language';
import FrontendURL from "./FrontendURL";
import ServerVersionChecker from "../../../common/component/ServerVersionChecker";
const StyledWrapper = styled.div`
position: relative;
width: 512px;
height: 100%;
display: flex;
flex-direction: column;
gap: 24px;
.logo {
display: flex;
gap: 16px;
.preview {
width: 96px;
height: 96px;
}
.upload {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
.tip {
font-weight: normal;
font-size: 14px;
line-height: 20px;
color: #374151;
}
}
}
.inputs {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 24px;
margin-bottom: 64px;
.input {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
}
> .setting {
font-size: 14px;
line-height: 20px;
> .label {
font-weight: 500;
}
> .tip {
font-weight: 400;
color: #667085;
display: flex;
width: 100%;
justify-content: space-between;
}
> form {
margin-top: 16px;
width: 512px;
}
}
`;
export default function Overview() {
const { t } = useTranslation("setting");
const { loginUser } = useAppSelector((store) => {
return { loginUser: store.authData.user };
});
const { values: loginConfig, updateConfig: updateLoginConfig } = useConfig("login");
const handleUpdateWhoCanSignUp = (value: WhoCanSignUp) => {
updateLoginConfig({ ...loginConfig, who_can_sign_up: value });
};
@@ -92,14 +26,15 @@ export default function Overview() {
if (!loginConfig) return null;
const { who_can_sign_up: whoCanSignUp, guest = false } = loginConfig as LoginConfig;
const isAdmin = loginUser?.is_admin;
return (
<StyledWrapper>
<div className="relative w-[512px] h-full flex flex-col gap-6">
<Server />
{isAdmin && (
<>
<div className="setting">
<p className="label">{t("overview.sign_up.title")}</p>
<p className="tip">{t("overview.sign_up.desc")}</p>
<div className="text-sm">
<p className="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")]}
values={["EveryOne", "InvitationOnly"]}
@@ -109,9 +44,9 @@ export default function Overview() {
}}
/>
</div>
<div className="setting">
<p className="label">{t("overview.guest_mode.title")}</p>
<p className="tip">
<div className="text-sm">
<p className="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")}
</span>
@@ -131,6 +66,6 @@ export default function Overview() {
</>
)}
<Language />
</StyledWrapper>
</div>
);
}
+4 -6
View File
@@ -10,12 +10,10 @@ const Index = () => {
i18n.changeLanguage(v);
};
return (
<div className="setting">
<p className="label">{t("overview.lang.title")}</p>
<p className="tip">
<span className="txt">
{t("overview.lang.desc")}
</span>
<div className="text-sm">
<p className="font-semibold">{t("overview.lang.title")}</p>
<p className="flex justify-between w-full text-gray-400 mb-2">
{t("overview.lang.desc")}
</p>
<StyledRadio
options={[t("overview.lang.en"), t("overview.lang.zh"), t("overview.lang.jp")]}
+7 -9
View File
@@ -9,7 +9,6 @@ import Textarea from "../../../common/component/styled/Textarea";
import SaveTip from '../../../common/component/SaveTip';
import { useAppSelector } from '../../../app/store';
const Index = () => {
const { t } = useTranslation("setting");
const { t: ct } = useTranslation();
@@ -62,8 +61,8 @@ const Index = () => {
if (!loginUser || !serverValues) return null;
return (
<>
<div className="logo">
<div className="preview">
<div className="flex gap-4">
<div className="w-24 h-24">
<LogoUploader
disabled={!isAdmin}
url={uploadSuccess ? `${logo}?t=${+new Date()}` : logo}
@@ -72,15 +71,15 @@ const Index = () => {
/>
</div>
{isAdmin && (
<div className="upload">
<div className="tip">
<div className="flex flex-col justify-between items-start">
<div className="text-sm text-gray-600">
{t("overview.upload_desc")}
</div>
</div>
)}
</div>
<div className="inputs">
<div className="input">
<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>
<Input
disabled={!isAdmin}
@@ -92,7 +91,7 @@ const Index = () => {
placeholder="Server Name"
/>
</div>
<div className="input">
<div className="w-full flex flex-col items-start gap-2">
<Label htmlFor="desc">{t("overview.desc")}</Label>
<Textarea
disabled={!isAdmin}
@@ -107,7 +106,6 @@ const Index = () => {
</div>
</div>
{changed && <SaveTip saveHandler={handleUpdateServer} resetHandler={handleReset} />}
</>
);
};
+4 -10
View File
@@ -1,11 +1,5 @@
import { useState, useEffect, ChangeEvent } from "react";
import styled from "styled-components";
const StyledTest = styled.div`
display: flex;
gap: 16px;
white-space: nowrap;
margin-top: 24px;
`;
import { useSendTestEmailMutation } from "../../../app/services/server";
import iconQuestion from "../../../assets/icons/question.svg?url";
import useConfig from "../../../common/hook/useConfig";
@@ -135,7 +129,7 @@ export default function ConfigSMTP() {
{t("how_to")}
</a>
</div>
<StyledTest>
<div className="flex gap-4 whitespace-nowrap mt-6">
<Input
type={"email"}
disabled={!enabled}
@@ -147,9 +141,9 @@ export default function ConfigSMTP() {
<Button disabled={!enabled || !testEmail} onClick={handleTestClick}>
{t("send_test_email")}
</Button>
</StyledTest>
</div>
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
{/* <button onClick={handleUpdate} className="btn">update</button> */}
</StyledContainer>
);
}
}
+1 -1
View File
@@ -25,7 +25,7 @@ const Index = ({ handleClose, hostId }: Props) => {
{/* message list */}
<main id='MESSAGE_LIST_CONTAINER' className="relative flex-1 overflow-y-auto scroll-smooth">
{/* placeholder */}
<div className="flex items-center h-10 justify-center"></div>
<div className="flex-center h-10"></div>
<Welcome needLogin={notLogin} />
{notLogin ? null : <MessageFeed hostId={hostId} />}
</main>