feat: i18n
This commit is contained in:
@@ -8,6 +8,7 @@ import ChannelIcon from "./ChannelIcon";
|
||||
import ChannelModal from "./ChannelModal";
|
||||
import UsersModal from "./UsersModal";
|
||||
import InviteModal from "./InviteModal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Styled = styled.ul`
|
||||
z-index: 999;
|
||||
@@ -42,6 +43,7 @@ const Styled = styled.ul`
|
||||
}
|
||||
`;
|
||||
export default function AddEntriesMenu() {
|
||||
const { t } = useTranslation();
|
||||
const currentUser = useAppSelector((store) => store.authData.user);
|
||||
const [isPrivate, setIsPrivate] = useState(false);
|
||||
const [inviteModalVisible, setInviteModalVisible] = useState(false);
|
||||
@@ -79,20 +81,20 @@ export default function AddEntriesMenu() {
|
||||
{currentUser?.is_admin && (
|
||||
<li className="item" onClick={handleOpenChannelModal.bind(null, false)}>
|
||||
<ChannelIcon className="icon" />
|
||||
New Channel
|
||||
{t("action.new_channel")}
|
||||
</li>
|
||||
)}
|
||||
<li className="item" onClick={handleOpenChannelModal.bind(null, true)}>
|
||||
<ChannelIcon personal={true} className="icon" />
|
||||
New Private Channel
|
||||
{t("action.new_private_channel")}
|
||||
</li>
|
||||
<li className="item" onClick={toggleUsersModalVisible}>
|
||||
<IconMention className="icon" />
|
||||
New Message
|
||||
{t("action.new_msg")}
|
||||
</li>
|
||||
<li className="item" onClick={toggleInviteModalVisible}>
|
||||
<IconInvite className="icon" />
|
||||
Invite People
|
||||
{t("action.invite_people")}
|
||||
</li>
|
||||
</Styled>
|
||||
{channelModalVisible && <ChannelModal personal={isPrivate} closeModal={handleCloseModal} />}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ChangeEvent, FC, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import Avatar from "./Avatar";
|
||||
import uploadIcon from "../../assets/icons/upload.image.svg?url";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
width: 96px;
|
||||
@@ -75,6 +76,7 @@ const AvatarUploader: FC<Props> = ({
|
||||
uploadImage,
|
||||
disabled = false
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const handleUpload = async (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
if (uploading) return;
|
||||
@@ -91,7 +93,7 @@ const AvatarUploader: FC<Props> = ({
|
||||
<Avatar type={type} url={url} name={name} />
|
||||
{!disabled && (
|
||||
<>
|
||||
<div className="tip">{uploading ? `Uploading` : `Change Avatar`}</div>
|
||||
<div className="tip">{uploading ? t("status.uploading") : t("action.change_avatar")}</div>
|
||||
<input
|
||||
multiple={false}
|
||||
onChange={handleUpload}
|
||||
|
||||
@@ -7,6 +7,7 @@ import IconInvite from "../../assets/icons/placeholder.invite.svg";
|
||||
import IconDownload from "../../assets/icons/placeholder.download.svg";
|
||||
import UsersModal from "./UsersModal";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
||||
interface Props {
|
||||
@@ -18,6 +19,7 @@ const classes = {
|
||||
boxTip: "px-5 text-sm text-[#475467] font-bold text-center"
|
||||
};
|
||||
const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
const { t } = useTranslation("welcome");
|
||||
const server = useAppSelector((store) => store.server);
|
||||
const [inviteModalVisible, setInviteModalVisible] = useState(false);
|
||||
const [createChannelVisible, setCreateChannelVisible] = useState(false);
|
||||
@@ -32,22 +34,21 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
setInviteModalVisible((prev) => !prev);
|
||||
};
|
||||
const chatTip =
|
||||
type == "chat" ? "Create a Channel to Start a Conversation" : "Send a Direct Message";
|
||||
type == "chat" ? t("start_by_channel") : t("start_by_dm");
|
||||
const chatHandler = type == "chat" ? toggleChannelModalVisible : toggleUserListVisible;
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-8 -mt-[50px]">
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<h2 className="text-3xl text-[#344054] font-bold">Welcome to {server.name} server</h2>
|
||||
<h2 className="text-3xl text-[#344054] font-bold">{t("title", { name: server.name })}</h2>
|
||||
<p className="text-sm text-[#98a2b3] max-w-[424px] text-center">
|
||||
Here are some steps to help you get started. For more, check out our Getting Started
|
||||
guide
|
||||
{t("desc")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 grid-rows-2 gap-6">
|
||||
<div className={classes.box} onClick={toggleInviteModalVisible}>
|
||||
<IconInvite className={classes.boxIcon} />
|
||||
<div className={classes.boxTip}>Invite your friends or teammates</div>
|
||||
<div className={classes.boxTip}>{t("invite")}</div>
|
||||
</div>
|
||||
<button onClick={chatHandler} className={classes.box} >
|
||||
<IconChat className={classes.boxIcon} />
|
||||
@@ -55,11 +56,11 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
</button>
|
||||
<a href={"https://voce.chat#download"} target={"_blank"} rel="noreferrer" className={classes.box} >
|
||||
<IconDownload className={classes.boxIcon} />
|
||||
<div className={classes.boxTip}>Download Mobile apps</div>
|
||||
<div className={classes.boxTip}>{t("download")}</div>
|
||||
</a>
|
||||
<a href={"https://voce.chat"} target={"_blank"} rel="noreferrer" className={classes.box} >
|
||||
<a href={"https://doc.voce.chat"} target={"_blank"} rel="noreferrer" className={classes.box} >
|
||||
<IconAsk className={classes.boxIcon} />
|
||||
<div className={classes.boxTip}>Got questions? Visit our help center</div>
|
||||
<div className={classes.boxTip}>{t("help")}</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import useFilteredUsers from "../../hook/useFilteredUsers";
|
||||
import { useCreateChannelMutation } from "../../../app/services/channel";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { CreateChannelDTO } from "../../../types/channel";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
personal?: boolean;
|
||||
@@ -19,6 +20,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
|
||||
const { t } = useTranslation("chat");
|
||||
const { usersData, loginUid } = useAppSelector((store) => {
|
||||
return { usersData: store.users.byId, loginUid: store.authData.user?.uid };
|
||||
});
|
||||
@@ -98,7 +100,7 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
|
||||
<input
|
||||
value={input}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Type Username to search"
|
||||
placeholder={t("search_user_placeholder") || ""}
|
||||
/>
|
||||
</div>
|
||||
{users && (
|
||||
@@ -129,21 +131,21 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
|
||||
</div>
|
||||
)}
|
||||
<div className={`right ${!is_public ? "private" : ""}`}>
|
||||
<h3 className="title">Create New Channel</h3>
|
||||
<h3 className="title">{t("create_channel")}</h3>
|
||||
<p className="desc normal">
|
||||
{!is_public
|
||||
? "This is a private channel, only select members will be able to join."
|
||||
: "This is a public channel, everyone in this team can see it."}
|
||||
? t("create_private_channel_desc")
|
||||
: t("create_channel_desc")}
|
||||
</p>
|
||||
<div className="name">
|
||||
<span className="label normal">Channel Name</span>
|
||||
<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>
|
||||
</div>
|
||||
<div className="private">
|
||||
<span className="txt normal">Private Channel</span>
|
||||
<span className="txt normal">{t("private_channel")}</span>
|
||||
<StyledToggle
|
||||
data-checked={!is_public}
|
||||
data-disabled={!loginUser?.is_admin}
|
||||
@@ -152,10 +154,10 @@ const ChannelModal: FC<Props> = ({ personal = false, closeModal }) => {
|
||||
</div>
|
||||
<div className="btns">
|
||||
<Button onClick={closeModal} className="normal cancel">
|
||||
Cancel
|
||||
{t("action.cancel", { ns: "common" })}
|
||||
</Button>
|
||||
<Button disabled={isLoading} onClick={handleCreate} className="normal">
|
||||
Create
|
||||
{t("action.create", { ns: "common" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -50,6 +50,7 @@ const StyledWrapper = styled.div`
|
||||
}
|
||||
}
|
||||
.right {
|
||||
min-width: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useGetServerVersionQuery } from "../../app/services/server";
|
||||
type Props = {};
|
||||
const FAQ: FC<Props> = () => {
|
||||
const { t } = useTranslation("setting", { keyPrefix: "faq" });
|
||||
const { data: serverVersion } = useGetServerVersionQuery();
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="item">Client Version: {process.env.VERSION}</div>
|
||||
<div className="item">Server Version: {serverVersion}</div>
|
||||
<div className="item">Build Timestamp: {process.env.REACT_APP_BUILD_TIME}</div>
|
||||
<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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { FC, useEffect } from "react";
|
||||
import IconGithub from "../../assets/icons/github.svg";
|
||||
import styled from "styled-components";
|
||||
import Button from "./styled/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledSocialButton = styled(Button)`
|
||||
width: 100%;
|
||||
@@ -25,6 +26,7 @@ type Props = {
|
||||
};
|
||||
|
||||
const GithubLoginButton: FC<Props> = ({ type = "login", source = "webapp", client_id }) => {
|
||||
const { t } = useTranslation("auth");
|
||||
useEffect(() => {
|
||||
const handleGithubLoginStatusChange = (evt: StorageEvent) => {
|
||||
const { key, newValue } = evt;
|
||||
@@ -60,7 +62,7 @@ const GithubLoginButton: FC<Props> = ({ type = "login", source = "webapp", clien
|
||||
return (
|
||||
<StyledSocialButton onClick={handleGithubLogin}>
|
||||
<IconGithub className="icon" />
|
||||
{` ${type === "login" ? "Sign in" : "Sign up"} with Github`}
|
||||
{` ${type === "login" ? t("login.github") : t("reg.github")}`}
|
||||
</StyledSocialButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import { KEY_LOCAL_MAGIC_TOKEN } from "../../app/config";
|
||||
import IconGoogle from "../../assets/icons/google.svg";
|
||||
import Button from "./styled/Button";
|
||||
import { useLoginMutation } from "../../app/services/auth";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledSocialButton = styled(Button)`
|
||||
position: relative;
|
||||
@@ -59,6 +60,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const GoogleLoginInner: FC<Props> = ({ type = "login", loaded, loadError }) => {
|
||||
const { t } = useTranslation("auth");
|
||||
const [login, { isSuccess, isLoading, error }] = useLoginMutation();
|
||||
//拿本地存的magic token
|
||||
const magic_token = localStorage.getItem(KEY_LOCAL_MAGIC_TOKEN);
|
||||
@@ -91,7 +93,7 @@ const GoogleLoginInner: FC<Props> = ({ type = "login", loaded, loadError }) => {
|
||||
{loadError
|
||||
? "Script Load Error!"
|
||||
: loaded
|
||||
? `${type === "login" ? "Sign in" : "Sign up"} with Google`
|
||||
? `${type === "login" ? t("login.google") : t("reg.google")}`
|
||||
: `Initializing`}
|
||||
</div>
|
||||
<div className="hide">
|
||||
|
||||
@@ -3,6 +3,7 @@ import useInviteLink from "../hook/useInviteLink";
|
||||
import Input from "./styled/Input";
|
||||
import Button from "./styled/Button";
|
||||
import { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
@@ -40,6 +41,7 @@ const StyledWrapper = styled.div`
|
||||
`;
|
||||
type Props = {};
|
||||
const InviteLink: FC<Props> = () => {
|
||||
const { t } = useTranslation("chat");
|
||||
const { generating, link, linkCopied, copyLink, generateNewLink } = useInviteLink();
|
||||
const handleNewLink = () => {
|
||||
generateNewLink();
|
||||
@@ -47,16 +49,16 @@ const InviteLink: FC<Props> = () => {
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<span className="tip">Share this link to invite people to this server.</span>
|
||||
<span className="tip">{t("share_invite_link")}</span>
|
||||
<div className="link">
|
||||
<Input readOnly className={"large"} placeholder="Generating" value={link} />
|
||||
<Button onClick={copyLink} className="ghost small border_less">
|
||||
{linkCopied ? "Copied" : `Copy`}
|
||||
{linkCopied ? "Copied" : t("action.copy", { ns: "common" })}
|
||||
</Button>
|
||||
</div>
|
||||
<span className="sub_tip">Invite link expires in 7 days.</span>
|
||||
<span className="sub_tip">{t("invite_link_expire")}</span>
|
||||
<Button className="ghost" disabled={generating} onClick={handleNewLink}>
|
||||
{generating ? `Generating` : `Generate New Link`}
|
||||
{generating ? `Generating` : t("generate_new_link")}
|
||||
</Button>
|
||||
</StyledWrapper>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect, useState, ChangeEvent, FC } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import useInviteLink from "../../hook/useInviteLink";
|
||||
import Button from "../styled/Button";
|
||||
@@ -59,6 +60,7 @@ const Styled = styled.div`
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
button {
|
||||
margin-left: 4px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #22ccee;
|
||||
@@ -71,6 +73,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const InviteByEmail: FC<Props> = ({ cid }) => {
|
||||
const { t } = useTranslation("chat");
|
||||
const [email, setEmail] = useState("");
|
||||
const { enableSMTP, linkCopied, link, copyLink, generateNewLink, generating } =
|
||||
useInviteLink(cid);
|
||||
@@ -87,33 +90,33 @@ const InviteByEmail: FC<Props> = ({ cid }) => {
|
||||
return (
|
||||
<Styled>
|
||||
<div className="invite">
|
||||
<label htmlFor="">Invite by Email</label>
|
||||
<label htmlFor="">{t("invite_by_email")}</label>
|
||||
<div className="input">
|
||||
<Input
|
||||
value={email}
|
||||
onChange={handleEmail}
|
||||
disabled={!enableSMTP}
|
||||
type="email"
|
||||
placeholder={enableSMTP ? "Enter Email" : "Enable SMTP First"}
|
||||
placeholder={enableSMTP ? "Enter Email" : t("enable_smtp") || ""}
|
||||
/>
|
||||
<Button disabled={!enableSMTP || !email} className="send">
|
||||
Send
|
||||
{t("action.send", { ns: "common" })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="link">
|
||||
<label htmlFor="">Or Send invite link to your friends</label>
|
||||
<label htmlFor="">{t("send_invite_link")}</label>
|
||||
<div className="input">
|
||||
<Input readOnly className="invite" placeholder="Generating" value={link} />
|
||||
<button className="copy" onClick={copyLink}>
|
||||
Copy
|
||||
{t("action.copy", { ns: "common" })}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tip">
|
||||
Invite link expires in 7 days.{" "}
|
||||
{t("invite_link_expire")}
|
||||
<button disabled={generating} className="new" onClick={() => generateNewLink()}>
|
||||
Generate New Link
|
||||
{t("generate_new_link")}
|
||||
</button>
|
||||
</div>
|
||||
</Styled>
|
||||
|
||||
@@ -5,6 +5,7 @@ import CloseIcon from "../../../assets/icons/close.svg";
|
||||
import Modal from "../Modal";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Styled = styled.div`
|
||||
display: flex;
|
||||
@@ -37,6 +38,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const InviteModal: FC<Props> = ({ type = "server", cid, title = "", closeModal }) => {
|
||||
const { t } = useTranslation("chat");
|
||||
const { channel, server } = useAppSelector((store) => {
|
||||
return {
|
||||
channel: cid ? store.channels.byId[cid] : undefined,
|
||||
@@ -48,7 +50,7 @@ const InviteModal: FC<Props> = ({ type = "server", cid, title = "", closeModal }
|
||||
<Modal>
|
||||
<Styled>
|
||||
<h2 className="title">
|
||||
Add friends to {finalTitle}
|
||||
{t("invite_title", { name: finalTitle })}
|
||||
<CloseIcon className="close" onClick={closeModal} />
|
||||
</h2>
|
||||
{!channel?.is_public && <AddMembers cid={cid} closeModal={closeModal} />}
|
||||
|
||||
@@ -5,6 +5,7 @@ import useLeaveChannel from "../../hook/useLeaveChannel";
|
||||
import Modal from "../Modal";
|
||||
import StyledModal from "../styled/Modal";
|
||||
import Button from "../styled/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
id: number;
|
||||
@@ -13,6 +14,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const LeaveConfirmModal: FC<Props> = ({ id, closeModal, handleNextStep }) => {
|
||||
const { t } = useTranslation("setting");
|
||||
const navigateTo = useNavigate();
|
||||
const { isOwner, leaving, leaveChannel, leaveSuccess } = useLeaveChannel(id);
|
||||
|
||||
@@ -28,16 +30,16 @@ const LeaveConfirmModal: FC<Props> = ({ id, closeModal, handleNextStep }) => {
|
||||
<Modal id="modal-modal">
|
||||
<StyledModal
|
||||
className="compact"
|
||||
title="Leave Channel"
|
||||
title={t("channel.leave") || ""}
|
||||
description={
|
||||
isOwner
|
||||
? "You need to transfer your channel ownership to someone else before leaving the channel."
|
||||
: "Are you sure want to leave this channel?"
|
||||
? t("channel.transfer_desc") || ""
|
||||
: t("channel.leave_desc") || ""
|
||||
}
|
||||
buttons={
|
||||
<>
|
||||
<Button onClick={closeModal.bind(null, undefined)} className="cancel">
|
||||
Cancel
|
||||
{t("action.cancel", { ns: "common" })}
|
||||
</Button>
|
||||
{isOwner ? (
|
||||
<Button onClick={handleNextStep} className="main">
|
||||
|
||||
@@ -7,6 +7,7 @@ import useLeaveChannel from "../../hook/useLeaveChannel";
|
||||
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;
|
||||
@@ -37,6 +38,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const TransferOwnerModal: FC<Props> = ({ id, closeModal, withLeave = true }) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
transferOwner,
|
||||
otherMembers,
|
||||
@@ -80,7 +82,7 @@ const TransferOwnerModal: FC<Props> = ({ id, closeModal, withLeave = true }) =>
|
||||
buttons={
|
||||
<>
|
||||
<Button onClick={closeModal.bind(null, undefined)} className="cancel">
|
||||
Cancel
|
||||
{t("action.cancel")}
|
||||
</Button>
|
||||
<Button disabled={!uid} onClick={handleTransferAndLeave} className="danger">
|
||||
{operating ? "Assigning" : `Assign and Leave`}
|
||||
|
||||
@@ -13,11 +13,13 @@ import IconArrowDown from "../../../assets/icons/arrow.down.mini.svg";
|
||||
import IconCheck from "../../../assets/icons/check.sign.svg";
|
||||
import useUserOperation from "../../hook/useUserOperation";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
cid?: number;
|
||||
}
|
||||
const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
const { t } = useTranslation(["member", "common"]);
|
||||
const { users, channels, loginUser } = useAppSelector((store) => {
|
||||
return {
|
||||
users: store.users,
|
||||
@@ -54,9 +56,9 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
<StyledWrapper>
|
||||
{loginUser?.is_admin && <InviteLink />}
|
||||
<div className="intro">
|
||||
<h4 className="title">Manage Members</h4>
|
||||
<h4 className="title">{t("manage_members")}</h4>
|
||||
<p className="desc">
|
||||
Disabling your account means you can recover it at any time after taking this action.
|
||||
{t("manage_tip")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -84,7 +86,7 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
</div>
|
||||
<div className="right">
|
||||
<span className="role">
|
||||
{is_admin ? "Admin" : "User"}
|
||||
{is_admin ? t("admin") : t("user")}
|
||||
{switchRoleVisible && (
|
||||
<Tippy
|
||||
interactive
|
||||
@@ -100,7 +102,7 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
isAdmin: true
|
||||
})}
|
||||
>
|
||||
Admin
|
||||
{t("admin")}
|
||||
{is_admin && <IconCheck className="icon" />}
|
||||
</li>
|
||||
<li
|
||||
@@ -111,7 +113,7 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
isAdmin: false
|
||||
})}
|
||||
>
|
||||
User
|
||||
{t("user")}
|
||||
{!is_admin && <IconCheck className="icon" />}
|
||||
</li>
|
||||
</StyledMenu>
|
||||
@@ -130,17 +132,17 @@ const ManageMembers: FC<Props> = ({ cid }) => {
|
||||
<StyledMenu className="menu">
|
||||
{email && (
|
||||
<li className="item" onClick={copyEmail.bind(null, email)}>
|
||||
Copy Email
|
||||
{t("action.copy_email", { ns: "common" })}
|
||||
</li>
|
||||
)}
|
||||
{canRemoveFromChannel && (
|
||||
<li className="item danger" onClick={removeFromChannel.bind(null, uid)}>
|
||||
Remove From Channel
|
||||
{t("remove_from_channel")}
|
||||
</li>
|
||||
)}
|
||||
{canRemove && (
|
||||
<li className="item danger" onClick={removeUser.bind(null, uid)}>
|
||||
Remove
|
||||
{t("action.remove", { ns: "common" })}
|
||||
</li>
|
||||
)}
|
||||
</StyledMenu>
|
||||
|
||||
@@ -20,6 +20,7 @@ import IconSelect from "../../../assets/icons/select.svg";
|
||||
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;
|
||||
@@ -68,6 +69,7 @@ type Props = {
|
||||
toggleEditMessage: () => void;
|
||||
};
|
||||
const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleEditMessage }) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
canDelete,
|
||||
canReply,
|
||||
@@ -134,27 +136,27 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
|
||||
content={<ReactionPicker mid={mid} hidePicker={hideAll} />}
|
||||
>
|
||||
<li className="cmd">
|
||||
<Tooltip placement="top" tip="Add Reaction">
|
||||
<Tooltip placement="top" tip={t("action.add_reaction")}>
|
||||
<img src={reactIcon} className="toggler" alt="icon emoji" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
</Tippy>
|
||||
{canEdit && (
|
||||
<li className="cmd" onClick={toggleEditMessage}>
|
||||
<Tooltip placement="top" tip="Edit">
|
||||
<Tooltip placement="top" tip={t("action.edit")}>
|
||||
<img src={editIcon} alt="icon edit" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
)}
|
||||
{canReply && (
|
||||
<li className="cmd" onClick={handleReply}>
|
||||
<Tooltip placement="top" tip="Reply">
|
||||
<Tooltip placement="top" tip={t("action.reply")}>
|
||||
<img src={replyIcon} alt="icon reply" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
)}
|
||||
<li className="cmd fav" onClick={handleAddFav}>
|
||||
<Tooltip placement="top" tip="Add to Favorites">
|
||||
<Tooltip placement="top" tip={t("action.add_to_fav")}>
|
||||
<IconBookmark />
|
||||
</Tooltip>
|
||||
</li>
|
||||
@@ -169,22 +171,22 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
|
||||
items={
|
||||
[
|
||||
canPin && {
|
||||
title: pinned ? `Unpin Message` : `Pin Message`,
|
||||
title: pinned ? t("action.unpin") : t("action.pin"),
|
||||
icon: <IconPin className="icon" />,
|
||||
handler: pinned ? handleUnpin : togglePinModal
|
||||
},
|
||||
{
|
||||
title: "Forward",
|
||||
title: t("action.forward"),
|
||||
icon: <IconForward className="icon" />,
|
||||
handler: toggleForwardModal
|
||||
},
|
||||
{
|
||||
title: "Select",
|
||||
title: t("action.select"),
|
||||
icon: <IconSelect className="icon" />,
|
||||
handler: handleSelect.bind(null, mid)
|
||||
},
|
||||
canDelete && {
|
||||
title: " Delete",
|
||||
title: t("action.remove"),
|
||||
danger: true,
|
||||
icon: <IconDelete className="icon" />,
|
||||
handler: toggleDeleteModal
|
||||
@@ -195,7 +197,7 @@ const Commands: FC<Props> = ({ context = "user", contextId = 0, mid = 0, toggleE
|
||||
}
|
||||
>
|
||||
<li className="cmd">
|
||||
<Tooltip placement="top" tip="More">
|
||||
<Tooltip placement="top" tip={t("more")}>
|
||||
<img src={moreIcon} alt="icon more" />
|
||||
</Tooltip>
|
||||
</li>
|
||||
|
||||
@@ -12,6 +12,7 @@ import IconSelect from "../../../assets/icons/select.svg";
|
||||
import { updateSelectMessages } from "../../../app/slices/ui";
|
||||
import useSendMessage from "../../hook/useSendMessage";
|
||||
import useMessageOperation from "./useMessageOperation";
|
||||
import { useTranslation } from "react-i18next";
|
||||
type Props = {
|
||||
context: "user" | "channel";
|
||||
contextId: number;
|
||||
@@ -30,6 +31,7 @@ const MessageContextMenu: FC<Props> = ({
|
||||
editMessage,
|
||||
children
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
copyContent,
|
||||
canEdit,
|
||||
@@ -58,37 +60,37 @@ const MessageContextMenu: FC<Props> = ({
|
||||
};
|
||||
const items = [
|
||||
canEdit && {
|
||||
title: "Edit Message",
|
||||
title: t("action.edit_msg"),
|
||||
icon: <IconEdit className="icon" />,
|
||||
handler: editMessage
|
||||
},
|
||||
canReply && {
|
||||
title: "Reply",
|
||||
title: t("action.reply"),
|
||||
icon: <IconReply className="icon" />,
|
||||
handler: handleReply
|
||||
},
|
||||
canCopy && {
|
||||
title: "Copy",
|
||||
title: t("action.copy"),
|
||||
icon: <IconCopy className="icon" />,
|
||||
handler: copyContent
|
||||
},
|
||||
canPin && {
|
||||
title: pinned ? "Unpin" : "Pin",
|
||||
title: pinned ? t("action.unpin") : t("action.pin"),
|
||||
icon: <IconPin className="icon" />,
|
||||
handler: pinned ? unPin.bind(null, mid) : togglePinModal
|
||||
},
|
||||
{
|
||||
title: "Forward",
|
||||
title: t("action.forward"),
|
||||
icon: <IconForward className="icon" />,
|
||||
handler: toggleForwardModal
|
||||
},
|
||||
{
|
||||
title: "Select",
|
||||
title: t("action.select"),
|
||||
icon: <IconSelect className="icon" />,
|
||||
handler: handleSelect
|
||||
},
|
||||
canDelete && {
|
||||
title: "Delete",
|
||||
title: t("action.remove"),
|
||||
danger: true,
|
||||
icon: <IconDelete className="icon" />,
|
||||
handler: toggleDeleteModal
|
||||
|
||||
@@ -5,6 +5,7 @@ import renderContent from "./renderContent";
|
||||
import Avatar from "../Avatar";
|
||||
import IconForward from "../../../assets/icons/forward.svg";
|
||||
import useNormalizeMessage from "../../hook/useNormalizeMessage";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledForward = styled.div`
|
||||
display: flex;
|
||||
@@ -36,6 +37,7 @@ type Props = {
|
||||
id: string;
|
||||
};
|
||||
const ForwardedMessage: FC<Props> = ({ context, to, from_uid, id }) => {
|
||||
const { t } = useTranslation();
|
||||
const { normalizeMessage, messages } = useNormalizeMessage();
|
||||
const [forwards, setForwards] = useState<ReactElement | null>(null);
|
||||
useEffect(() => {
|
||||
@@ -52,7 +54,7 @@ const ForwardedMessage: FC<Props> = ({ context, to, from_uid, id }) => {
|
||||
<StyledForward data-forwarded-mids={forward_mids.join(",")}>
|
||||
<h4 className="tip">
|
||||
<IconForward className="icon" />
|
||||
Forwarded
|
||||
{t("action.forward")}
|
||||
</h4>
|
||||
<div className="list">
|
||||
{messages.map((msg, idx) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import StyledModal from "../styled/Modal";
|
||||
import Button from "../styled/Button";
|
||||
import Modal from "../Modal";
|
||||
import PreviewMessage from "./PreviewMessage";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledPinModal = styled(StyledModal)`
|
||||
min-width: 406px;
|
||||
@@ -30,6 +31,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const PinMessageModal: FC<Props> = ({ closeModal, mid = 0, gid = 0 }) => {
|
||||
const { t } = useTranslation();
|
||||
const { channel, pinMessage, isPining, isSuccess } = usePinMessage(gid);
|
||||
const handlePin = () => {
|
||||
pinMessage(mid);
|
||||
@@ -51,14 +53,14 @@ const PinMessageModal: FC<Props> = ({ closeModal, mid = 0, gid = 0 }) => {
|
||||
buttons={
|
||||
<>
|
||||
<Button onClick={closeModal} className="cancel">
|
||||
Cancel
|
||||
{t("action.cancel")}
|
||||
</Button>
|
||||
<Button disabled={isPining} onClick={handlePin} className="main">
|
||||
{isPining ? "Pining" : `Pin It`}
|
||||
{isPining ? "Pining" : t("action.pin")}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
title="Pin It"
|
||||
title={t("action.pin") || ""}
|
||||
description={`Do you want to pin this message to #${channel?.name}`}
|
||||
>
|
||||
<PreviewMessage mid={mid} />
|
||||
|
||||
@@ -9,6 +9,7 @@ import StyledWrapper from "./styled";
|
||||
import StyledMenu from "../styled/Menu";
|
||||
import useUserOperation from "../../hook/useUserOperation";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
uid: number;
|
||||
@@ -17,6 +18,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
const { t } = useTranslation(["member", "common"]);
|
||||
const {
|
||||
canCall,
|
||||
call,
|
||||
@@ -56,14 +58,14 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
<NavLink to={`/chat/dm/${uid}`}>
|
||||
<li className="icon chat">
|
||||
<IconMessage />
|
||||
<span className="txt">Message</span>
|
||||
<span className="txt">{t("send_msg")}</span>
|
||||
</li>
|
||||
</NavLink>
|
||||
{/* <NavLink to={`#`}> */}
|
||||
{enableCall && (
|
||||
<li className="icon call" onClick={call}>
|
||||
<IconCall />
|
||||
<span className="txt">Call</span>
|
||||
<span className="txt">{t("call")}</span>
|
||||
</li>
|
||||
)}
|
||||
{/* </NavLink> */}
|
||||
@@ -79,22 +81,22 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
{enableCall && (
|
||||
<li className="item" onClick={call}>
|
||||
{/* <IconCall className="icon" /> */}
|
||||
Call
|
||||
{t("call")}
|
||||
</li>
|
||||
)}
|
||||
{canCopyEmail && (
|
||||
<li className="item" onClick={copyEmail.bind(undefined, email)}>
|
||||
Copy Email
|
||||
{t("copy_email")}
|
||||
</li>
|
||||
)}
|
||||
{canRemoveFromChannel && (
|
||||
<li className="item danger" onClick={removeFromChannel.bind(null, uid)}>
|
||||
Remove from Channel
|
||||
{t("remove_from_channel")}
|
||||
</li>
|
||||
)}
|
||||
{canRemoveFromServer && (
|
||||
<li className="item danger" onClick={removeUser.bind(null, uid)}>
|
||||
Remove from Server
|
||||
{t("remove")}
|
||||
</li>
|
||||
)}
|
||||
</StyledMenu>
|
||||
@@ -102,7 +104,7 @@ const Profile: FC<Props> = ({ uid, type = "embed", cid }) => {
|
||||
>
|
||||
<li className={`icon more ${hasMore ? "" : "disabled"}`}>
|
||||
<IconMore />
|
||||
<span className="txt">More</span>
|
||||
<span className="txt">{t("more", { ns: "common" })}</span>
|
||||
</li>
|
||||
</Tippy>
|
||||
</ul>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { FC, MouseEvent } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
@@ -54,15 +55,16 @@ interface Props {
|
||||
}
|
||||
|
||||
const SaveTip: FC<Props> = ({ saveHandler, resetHandler }) => {
|
||||
const { t } = useTranslation("setting");
|
||||
return (
|
||||
<StyledWrapper className="animate__animated animate__flipInX animate__faster">
|
||||
<span className="txt">You have unsaved changes!</span>
|
||||
<span className="txt">{t('save_tip')}</span>
|
||||
<div className="btns">
|
||||
<button className="btn reset" onClick={resetHandler}>
|
||||
Reset
|
||||
{t('reset')}
|
||||
</button>
|
||||
<button className="btn" onClick={saveHandler}>
|
||||
Save Changes
|
||||
{t('save_change')}
|
||||
</button>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
|
||||
@@ -5,6 +5,7 @@ import addIcon from "../../assets/icons/add.svg?url";
|
||||
import AddEntriesMenu from "./AddEntriesMenu";
|
||||
import Tooltip from "./Tooltip";
|
||||
import { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
position: relative;
|
||||
@@ -35,13 +36,14 @@ const StyledWrapper = styled.div`
|
||||
`;
|
||||
type Props = {};
|
||||
const Search: FC<Props> = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="search">
|
||||
<img src={searchIcon} alt="search icon" />
|
||||
<input placeholder="Search..." className="input" />
|
||||
<input placeholder={`${t("action.search")}...`} className="input" />
|
||||
</div>
|
||||
<Tooltip tip="More" placement="bottom">
|
||||
<Tooltip tip={t("more")} placement="bottom">
|
||||
<Tippy interactive placement="bottom-end" trigger="click" content={<AddEntriesMenu />}>
|
||||
<img src={addIcon} alt="add icon" className="add" />
|
||||
</Tippy>
|
||||
|
||||
@@ -6,6 +6,7 @@ import MarkdownIcon from "../../../assets/icons/markdown.svg";
|
||||
import FullscreenIcon from "../../../assets/icons/fullscreen.svg";
|
||||
import ExitFullscreenIcon from "../../../assets/icons/fullscreen.exit.svg";
|
||||
import useUploadFile from "../../hook/useUploadFile";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Styled = styled.div`
|
||||
display: flex;
|
||||
@@ -58,6 +59,7 @@ const Toolbar: FC<Props> = ({
|
||||
to,
|
||||
context
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { addStageFile } = useUploadFile({ context, id: to });
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const handleUpload = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -94,7 +96,7 @@ const Toolbar: FC<Props> = ({
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
<Tooltip placement="top" tip="Upload">
|
||||
<Tooltip placement="top" tip={t("action.upload")}>
|
||||
<div className="add">
|
||||
<AddIcon />
|
||||
<label htmlFor="file">
|
||||
|
||||
@@ -15,6 +15,7 @@ import MixedInput, { useMixedEditor } from "../MixedInput";
|
||||
import useDraft from "../../hook/useDraft";
|
||||
import useUploadFile from "../../hook/useUploadFile";
|
||||
import { useAppDispatch, useAppSelector } from "../../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Modes = {
|
||||
text: "text",
|
||||
@@ -29,6 +30,7 @@ const Send: FC<IProps> = ({
|
||||
context = "channel",
|
||||
id
|
||||
}) => {
|
||||
const { t } = useTranslation("chat");
|
||||
const { resetStageFiles } = useUploadFile({ context, id });
|
||||
const { getDraft, getUpdateDraft } = useDraft({ context, id });
|
||||
const editor = useMixedEditor(`${context}_${id}`);
|
||||
@@ -130,14 +132,13 @@ const Send: FC<IProps> = ({
|
||||
setMarkdownFullscreen((prev) => !prev);
|
||||
};
|
||||
const name = context == "channel" ? channelsData[id]?.name : usersData[id]?.name;
|
||||
const placeholder = `Send to ${ChatPrefixes[context]}${name} `;
|
||||
const placeholder = `${t("send_to")} ${ChatPrefixes[context]}${name} `;
|
||||
const members =
|
||||
context == "channel" ? (channelsData[id]?.is_public ? uids : channelsData[id]?.members) : [];
|
||||
return (
|
||||
<StyledSend
|
||||
className={`send ${mode} ${markdownFullscreen ? "fullscreen" : ""} ${
|
||||
replying_mid ? "reply" : ""
|
||||
} ${context}`}
|
||||
className={`send ${mode} ${markdownFullscreen ? "fullscreen" : ""} ${replying_mid ? "reply" : ""
|
||||
} ${context}`}
|
||||
>
|
||||
{replying_mid && <Replying context={context} mid={replying_mid} id={id} />}
|
||||
{mode == Modes.text && <UploadFileList context={context} id={id} />}
|
||||
|
||||
@@ -5,6 +5,7 @@ import addIcon from "../../assets/icons/add.svg?url";
|
||||
import Tooltip from "./Tooltip";
|
||||
import AddEntriesMenu from "./AddEntriesMenu";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
min-height: 56px;
|
||||
@@ -54,6 +55,7 @@ type Props = {
|
||||
readonly?: boolean;
|
||||
};
|
||||
export default function Server({ readonly = false }: Props) {
|
||||
const { t } = useTranslation();
|
||||
const { pathname } = useLocation();
|
||||
const { server, userCount } = useAppSelector((store) => {
|
||||
return {
|
||||
@@ -74,7 +76,7 @@ export default function Server({ readonly = false }: Props) {
|
||||
<h3 className="name" title={description}>
|
||||
{name}
|
||||
</h3>
|
||||
<span className="desc">{userCount} members</span>
|
||||
<span className="desc">{userCount} {t("members")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
@@ -91,12 +93,12 @@ export default function Server({ readonly = false }: Props) {
|
||||
<h3 className="name" title={description}>
|
||||
{name}
|
||||
</h3>
|
||||
<span className="desc">{userCount} members</span>
|
||||
<span className="desc">{userCount} {t("members")}</span>
|
||||
</div>
|
||||
</div>
|
||||
</NavLink>
|
||||
|
||||
<Tooltip tip="More" placement="bottom">
|
||||
<Tooltip tip={t("more")} placement="bottom">
|
||||
<Tippy interactive placement="bottom-end" trigger="click" content={<AddEntriesMenu />}>
|
||||
<img src={addIcon} alt="add icon" className="add" />
|
||||
</Tippy>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { FC, ReactElement } from "react";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import useUserOperation from "../../hook/useUserOperation";
|
||||
import ContextMenu, { Item } from "../ContextMenu";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface Props {
|
||||
enable?: boolean;
|
||||
@@ -13,6 +14,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const UserContextMenu: FC<Props> = ({ enable = false, uid, cid, visible, hide, children }) => {
|
||||
const { t } = useTranslation("member");
|
||||
const {
|
||||
canCall,
|
||||
call,
|
||||
@@ -43,25 +45,25 @@ const UserContextMenu: FC<Props> = ({ enable = false, uid, cid, visible, hide, c
|
||||
items={
|
||||
[
|
||||
{
|
||||
title: "Message",
|
||||
title: t("send_msg"),
|
||||
handler: startChat
|
||||
},
|
||||
canCall && {
|
||||
title: "Call",
|
||||
title: t("call"),
|
||||
handler: call
|
||||
},
|
||||
canCopyEmail && {
|
||||
title: "Copy Email",
|
||||
title: t("copy_email"),
|
||||
handler: copyEmail
|
||||
},
|
||||
canRemoveFromChannel && {
|
||||
danger: true,
|
||||
title: "Remove From Channel",
|
||||
title: t("remove_from_channel"),
|
||||
handler: removeFromChannel
|
||||
},
|
||||
canRemove && {
|
||||
danger: true,
|
||||
title: "Remove",
|
||||
title: t("remove"),
|
||||
handler: removeUser
|
||||
}
|
||||
].filter(Boolean) as Item[]
|
||||
|
||||
@@ -6,6 +6,7 @@ import useFilteredUsers from "../hook/useFilteredUsers";
|
||||
|
||||
import User from "./User";
|
||||
import Modal from "./Modal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
@@ -56,6 +57,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const UsersModal: FC<Props> = ({ closeModal }) => {
|
||||
const { t } = useTranslation("chat");
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const { users, updateInput, input } = useFilteredUsers();
|
||||
useOutsideClick(wrapperRef, closeModal);
|
||||
@@ -68,7 +70,7 @@ const UsersModal: FC<Props> = ({ closeModal }) => {
|
||||
<Modal>
|
||||
<StyledWrapper ref={wrapperRef}>
|
||||
<div className="search">
|
||||
<input value={input} onChange={handleSearch} placeholder="Type Username to search" />
|
||||
<input value={input} onChange={handleSearch} placeholder={t("search_user_placeholder") || ""} />
|
||||
</div>
|
||||
{users && (
|
||||
<ul className="users">
|
||||
|
||||
@@ -12,6 +12,7 @@ const StyledButton = styled.button`
|
||||
line-height: 20px;
|
||||
color: #fff;
|
||||
background-color: #22ccee;
|
||||
word-break: keep-all;
|
||||
&.flex {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ 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;
|
||||
@@ -41,6 +42,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const Select: FC<Props> = ({ options = [], updateSelect = null, current = null }) => {
|
||||
const { t } = useTranslation();
|
||||
const [optionsVisible, setOptionsVisible] = useState(false);
|
||||
const [curr, setCurr] = useState<Partial<Option> | null>(null);
|
||||
const toggleVisible = () => {
|
||||
@@ -79,7 +81,7 @@ const Select: FC<Props> = ({ options = [], updateSelect = null, current = null }
|
||||
}
|
||||
>
|
||||
<Styled onClick={toggleVisible}>
|
||||
<span className="txt">{(current !== null ? current : curr)?.title || "Select"}</span>
|
||||
<span className="txt">{(current !== null ? current : curr)?.title || t("action.select")}</span>
|
||||
<IconArrow className="icon" />
|
||||
</Styled>
|
||||
</Tippy>
|
||||
|
||||
Reference in New Issue
Block a user