refactor: more tailwind
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vocechat-web",
|
||||
"version": "0.3.38",
|
||||
"version": "0.3.39",
|
||||
"private": true,
|
||||
"homepage": "https://voce.chat",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<svg width="14" height="15" viewBox="0 0 14 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 6.7929L10.5355 3.25736C10.7308 3.0621 11.0474 3.0621 11.2426 3.25736C11.4379 3.45262 11.4379 3.76921 11.2426 3.96447L7.70711 7.5L11.2426 11.0355C11.4379 11.2308 11.4379 11.5474 11.2426 11.7426C11.0474 11.9379 10.7308 11.9379 10.5355 11.7426L7 8.20711L3.46447 11.7426C3.26921 11.9379 2.95262 11.9379 2.75736 11.7426C2.5621 11.5474 2.5621 11.2308 2.75736 11.0355L6.2929 7.5L2.75736 3.96447C2.5621 3.76921 2.5621 3.45262 2.75736 3.25736C2.95262 3.0621 3.26921 3.0621 3.46447 3.25736L7 6.7929Z" fill="black" fill-opacity="0.5"/>
|
||||
<svg width="14" height="15" viewBox="0 0 14 15" fill="black" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7 6.7929L10.5355 3.25736C10.7308 3.0621 11.0474 3.0621 11.2426 3.25736C11.4379 3.45262 11.4379 3.76921 11.2426 3.96447L7.70711 7.5L11.2426 11.0355C11.4379 11.2308 11.4379 11.5474 11.2426 11.7426C11.0474 11.9379 10.7308 11.9379 10.5355 11.7426L7 8.20711L3.46447 11.7426C3.26921 11.9379 2.95262 11.9379 2.75736 11.7426C2.5621 11.5474 2.5621 11.2308 2.75736 11.0355L6.2929 7.5L2.75736 3.96447C2.5621 3.76921 2.5621 3.45262 2.75736 3.25736C2.95262 3.0621 3.26921 3.0621 3.46447 3.25736L7 6.7929Z" fill-opacity="0.8"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 640 B After Width: | Height: | Size: 629 B |
@@ -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="M16.5303 11.7204L16.4462 11.6478C16.1526 11.4299 15.7359 11.4541 15.4697 11.7204L12.7503 14.4397L12.75 7.75003L12.7432 7.64826C12.6935 7.28218 12.3797 7.00003 12 7.00003L11.8982 7.00688C11.5322 7.05654 11.25 7.37033 11.25 7.75003L11.2503 14.4417L8.52961 11.7198L8.4455 11.6472C8.15193 11.4293 7.73527 11.4534 7.46895 11.7196C7.176 12.0124 7.17591 12.4873 7.46875 12.7803L11.4687 16.7818L11.5529 16.8544C11.8465 17.0724 12.2632 17.0482 12.5295 16.7819L16.5303 12.7811L16.6029 12.6969C16.8208 12.4033 16.7966 11.9867 16.5303 11.7204ZM6.25 3C4.45507 3 3 4.45507 3 6.25V17.75C3 19.5449 4.45507 21 6.25 21H17.75C19.5449 21 21 19.5449 21 17.75V6.25C21 4.45507 19.5449 3 17.75 3H6.25ZM4.5 6.25C4.5 5.2835 5.2835 4.5 6.25 4.5H17.75C18.7165 4.5 19.5 5.2835 19.5 6.25V17.75C19.5 18.7165 18.7165 19.5 17.75 19.5H6.25C5.2835 19.5 4.5 18.7165 4.5 17.75V6.25Z" fill="#fff"/>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="#fff" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16.5303 11.7204L16.4462 11.6478C16.1526 11.4299 15.7359 11.4541 15.4697 11.7204L12.7503 14.4397L12.75 7.75003L12.7432 7.64826C12.6935 7.28218 12.3797 7.00003 12 7.00003L11.8982 7.00688C11.5322 7.05654 11.25 7.37033 11.25 7.75003L11.2503 14.4417L8.52961 11.7198L8.4455 11.6472C8.15193 11.4293 7.73527 11.4534 7.46895 11.7196C7.176 12.0124 7.17591 12.4873 7.46875 12.7803L11.4687 16.7818L11.5529 16.8544C11.8465 17.0724 12.2632 17.0482 12.5295 16.7819L16.5303 12.7811L16.6029 12.6969C16.8208 12.4033 16.7966 11.9867 16.5303 11.7204ZM6.25 3C4.45507 3 3 4.45507 3 6.25V17.75C3 19.5449 4.45507 21 6.25 21H17.75C19.5449 21 21 19.5449 21 17.75V6.25C21 4.45507 19.5449 3 17.75 3H6.25ZM4.5 6.25C4.5 5.2835 5.2835 4.5 6.25 4.5H17.75C18.7165 4.5 19.5 5.2835 19.5 6.25V17.75C19.5 18.7165 18.7165 19.5 17.75 19.5H6.25C5.2835 19.5 4.5 18.7165 4.5 17.75V6.25Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 973 B After Width: | Height: | Size: 962 B |
@@ -47,3 +47,10 @@
|
||||
---channel-text-color: #1c1c1e;
|
||||
---msg-text-color: #374151;
|
||||
}
|
||||
/* overrides */
|
||||
|
||||
#MARKDOWN_CONTAINER .toastui-editor-contents {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ const Avatar: FC<Props> = ({
|
||||
} else {
|
||||
return (
|
||||
<div
|
||||
className="rounded-full flex-center"
|
||||
className={`rounded-full flex-center ${rest.className || ""}`}
|
||||
style={{
|
||||
width,
|
||||
height,
|
||||
|
||||
@@ -1,69 +1,8 @@
|
||||
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<{ size: number }>`
|
||||
width: ${({ size }) => `${size}px`};
|
||||
height: ${({ size }) => `${size}px`};
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
.avatar {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background-color: #eee;
|
||||
img {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
input[type="file"] {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
opacity: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.tip {
|
||||
white-space: nowrap;
|
||||
padding: 4px;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
}
|
||||
&:hover .tip {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
.icon {
|
||||
display: none;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
&:hover .icon {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
import clsx from "clsx";
|
||||
|
||||
type UID = number;
|
||||
interface Props {
|
||||
@@ -102,16 +41,18 @@ const AvatarUploader: FC<Props> = ({
|
||||
setUploading(false);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<StyledWrapper size={size} className={className}>
|
||||
<div className="avatar">
|
||||
<Avatar width={size} height={size} type={type} src={url} name={name} className={className} />
|
||||
<div style={{ width: `${size}px`, height: `${size}px` }} className={clsx(className, "relative group")}>
|
||||
<div className="group overflow-hidden relative w-full h-full rounded-full bg-gray-50">
|
||||
<Avatar width={size} height={size} type={type} src={url} name={name} className={`${className} object-cover w-full h-full`} />
|
||||
{!disabled && (
|
||||
<>
|
||||
<div className="tip">
|
||||
<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">
|
||||
{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"
|
||||
multiple={false}
|
||||
onChange={handleUpload}
|
||||
type="file"
|
||||
@@ -122,8 +63,8 @@ const AvatarUploader: FC<Props> = ({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{!disabled && <img src={uploadIcon} alt="icon" className="icon" />}
|
||||
</StyledWrapper>
|
||||
{!disabled && <img src={uploadIcon} alt="icon" className="hidden w-7 h-7 absolute top-0 right-0 group-hover:block" />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ interface Props {
|
||||
type?: "chat" | "user";
|
||||
}
|
||||
const classes = {
|
||||
box: "w-[200px] 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] rounded-3xl flex-center flex-col gap-4",
|
||||
boxIcon: "w-10 h-10",
|
||||
boxTip: "px-5 text-sm text-[#475467] font-bold text-center"
|
||||
};
|
||||
@@ -45,7 +45,7 @@ const BlankPlaceholder: FC<Props> = ({ type = "chat" }) => {
|
||||
{t("desc")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 grid-rows-2 gap-6">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 grid-rows-2 gap-2 md:gap-6 mt-auto">
|
||||
<div className={classes.box} onClick={toggleInviteModalVisible}>
|
||||
<IconInvite className={classes.boxIcon} />
|
||||
<div className={classes.boxTip}>{t("invite")}</div>
|
||||
|
||||
@@ -1,50 +1,8 @@
|
||||
import { FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import Avatar from "./Avatar";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import clsx from "clsx";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
user-select: none;
|
||||
&.compact {
|
||||
padding: 0;
|
||||
}
|
||||
&.interactive {
|
||||
&:hover,
|
||||
&.active {
|
||||
background: rgba(116, 127, 141, 0.1);
|
||||
}
|
||||
}
|
||||
.avatar {
|
||||
cursor: pointer;
|
||||
width: ${({ size }: { size: number }) => `${size}px`};
|
||||
height: ${({ size }: { size: number }) => `${size}px`};
|
||||
position: relative;
|
||||
img {
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
.name {
|
||||
display: flex;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #52525b;
|
||||
.txt {
|
||||
max-width: 140px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
interactive?: boolean;
|
||||
@@ -63,12 +21,12 @@ const Channel: FC<Props> = ({ interactive = true, id, compact = false, avatarSiz
|
||||
|
||||
if (!channel) return null;
|
||||
const { name, members = [], is_public, icon } = channel;
|
||||
|
||||
return (
|
||||
<StyledWrapper
|
||||
size={avatarSize}
|
||||
className={`${interactive ? "interactive" : ""} ${compact ? "compact" : ""}`}
|
||||
<div
|
||||
className={clsx(`flex items-center justify-start gap-2 p-2 rounded-lg select-none`, compact && "p-0", interactive && "hover:bg-gray-500/10")}
|
||||
>
|
||||
<div className="avatar">
|
||||
<div className={`cursor-pointer relative`} style={{ width: `${avatarSize}px`, height: `${avatarSize}px` }}>
|
||||
<Avatar
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
@@ -76,14 +34,15 @@ const Channel: FC<Props> = ({ interactive = true, id, compact = false, avatarSiz
|
||||
src={icon}
|
||||
name={"#"}
|
||||
alt="avatar"
|
||||
className="!w-full !h-full rounded-full"
|
||||
/>
|
||||
</div>
|
||||
{!compact && (
|
||||
<div className="name">
|
||||
<span className="txt">{name}</span> ({is_public ? totalMemberCount : members.length})
|
||||
<div className="flex text-sm text-gray-500 font-semibold">
|
||||
<span className="max-w-[140px] whitespace-nowrap overflow-hidden text-ellipsis">{name}</span> ({is_public ? totalMemberCount : members.length})
|
||||
</div>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,4 @@
|
||||
import { useState, useEffect, FC } from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.div`
|
||||
background-color: #fff;
|
||||
height: 218px;
|
||||
padding: 15px 15px 0 15px;
|
||||
line-height: 1.4;
|
||||
overflow: scroll;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
url: string;
|
||||
@@ -28,7 +17,7 @@ const Doc: FC<Props> = ({ url }) => {
|
||||
}, [url]);
|
||||
if (!content) return null;
|
||||
|
||||
return <Styled>{content}</Styled>;
|
||||
return <div className="bg-white h-[218px] p-[15px] pb-0 whitespace-pre-wrap break-all">{content}</div>;
|
||||
};
|
||||
|
||||
export default Doc;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import Styled from "./styled";
|
||||
import ImageMessage from "./ImageMessage";
|
||||
import useRemoveLocalMessage from "../../hook/useRemoveLocalMessage";
|
||||
import useUploadFile from "../../hook/useUploadFile";
|
||||
@@ -12,6 +11,7 @@ import IconDownload from "../../../assets/icons/download.svg";
|
||||
import IconClose from "../../../assets/icons/close.circle.svg";
|
||||
import VideoMessage from "./VideoMessage";
|
||||
import AudioMessage from "./AudioMessage";
|
||||
import clsx from "clsx";
|
||||
|
||||
const isLocalFile = (content: string) => {
|
||||
return content.startsWith("blob:");
|
||||
@@ -117,7 +117,7 @@ const FileMessage: FC<Props> = ({
|
||||
removeLocalMessage(properties.local_id);
|
||||
};
|
||||
if (!properties) return null;
|
||||
const icon = getFileIcon(content_type, name);
|
||||
const icon = getFileIcon(content_type, name, "w-9 h-auto");
|
||||
|
||||
if (!content || !name) return null;
|
||||
|
||||
@@ -156,21 +156,21 @@ const FileMessage: FC<Props> = ({
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Styled className={`file_message ${sending ? "sending" : ""}`}>
|
||||
<div className="basic">
|
||||
<div className={clsx(`file_message bg-slate-50 border border-solid border-gray-300 box-border w-[370px] h-[66px] rounded-md`, sending && "opacity-90")}>
|
||||
<div className="p-2 flex items-center justify-between gap-2">
|
||||
{icon}
|
||||
<div className="info">
|
||||
<span className="name">{name}</span>
|
||||
<span className="details">
|
||||
<div className="flex flex-col gap-1 w-full overflow-hidden">
|
||||
<span className="font-semibold text-sm text-gray-800 whitespace-nowrap text-ellipsis">{name}</span>
|
||||
<span className="whitespace-nowrap text-xs text-gray-500 flex gap-4">
|
||||
{sending ? (
|
||||
<Progress value={progress} width={"80%"} />
|
||||
) : (
|
||||
<>
|
||||
<i className="size">{formatBytes(size)}</i>
|
||||
<i className="time">{dayjs(created_at).fromNow()}</i>
|
||||
<i>{formatBytes(size)}</i>
|
||||
<i>{dayjs(created_at).fromNow()}</i>
|
||||
{fromUser && (
|
||||
<i className="from">
|
||||
by <strong>{fromUser.name}</strong>
|
||||
<i>
|
||||
by <strong className="font-bold">{fromUser.name}</strong>
|
||||
</i>
|
||||
)}
|
||||
</>
|
||||
@@ -178,14 +178,14 @@ const FileMessage: FC<Props> = ({
|
||||
</span>
|
||||
</div>
|
||||
{sending ? (
|
||||
<IconClose className="cancel" onClick={handleCancel} />
|
||||
<IconClose className="cursor-pointer" onClick={handleCancel} />
|
||||
) : (
|
||||
<a className="download" download={name} href={`${content}&download=true`}>
|
||||
<IconDownload />
|
||||
<a className="whitespace-nowrap" download={name} href={`${content}&download=true`}>
|
||||
<IconDownload className="fill-gray-500" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</Styled>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const Styled = styled.div`
|
||||
background: #f3f4f6;
|
||||
border: 1px solid #d4d4d4;
|
||||
box-sizing: border-box;
|
||||
border-radius: 6px;
|
||||
width: 370px;
|
||||
height: 66px;
|
||||
&.sending {
|
||||
opacity: 0.9;
|
||||
}
|
||||
* {
|
||||
user-select: text;
|
||||
}
|
||||
.basic {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
.icon {
|
||||
width: 36px;
|
||||
height: 48px;
|
||||
|
||||
}
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
.name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #1c1c1e;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.details {
|
||||
white-space: nowrap;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #616161;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
.from strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
.download {
|
||||
white-space: nowrap;
|
||||
svg path{
|
||||
fill: #616161;
|
||||
}
|
||||
}
|
||||
.cancel {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Styled;
|
||||
@@ -5,7 +5,6 @@ import Button from "../styled/Button";
|
||||
import Input from "../styled/Input";
|
||||
import Channel from "../Channel";
|
||||
import User from "../User";
|
||||
// import Channel from "../Channel";
|
||||
import Reply from "../Message/Reply";
|
||||
import StyledWrapper from "./styled";
|
||||
import useForwardMessage from "../../hook/useForwardMessage";
|
||||
|
||||
@@ -1,23 +1,8 @@
|
||||
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%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
color: #344054;
|
||||
border: 1px solid #d0d5dd;
|
||||
background: none !important;
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
source?: "widget" | "webapp",
|
||||
@@ -60,10 +45,10 @@ const GithubLoginButton: FC<Props> = ({ type = "login", source = "webapp", clien
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledSocialButton onClick={handleGithubLogin}>
|
||||
<IconGithub className="icon" />
|
||||
<Button className="flex-center gap-3 ghost !text-gray-600 !border-[#d0d5dd]" onClick={handleGithubLogin}>
|
||||
<IconGithub className="w-6 h-6" />
|
||||
{` ${type === "login" ? t("login.github") : t("reg.github")}`}
|
||||
</StyledSocialButton>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
export default GithubLoginButton;
|
||||
|
||||
@@ -1,54 +1,12 @@
|
||||
import { FC, useEffect, useState } from "react";
|
||||
import { GoogleLogin, GoogleOAuthProvider } from "@react-oauth/google";
|
||||
import toast from "react-hot-toast";
|
||||
import styled from "styled-components";
|
||||
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;
|
||||
width: 100%;
|
||||
background: white !important;
|
||||
border: 1px solid #d0d5dd;
|
||||
color: #344054;
|
||||
height: 46px;
|
||||
overflow: hidden;
|
||||
.mask {
|
||||
background: inherit;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
z-index: 998;
|
||||
height: 40px;
|
||||
.icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
> .hide {
|
||||
left: 0;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
iframe {
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.hide {
|
||||
opacity: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
loadError?: boolean;
|
||||
@@ -86,16 +44,16 @@ const GoogleLoginInner: FC<Props> = ({ type = "login", loaded, loadError }) => {
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<StyledSocialButton disabled={!loaded || isLoading}>
|
||||
<div className="mask">
|
||||
<IconGoogle className="icon" />
|
||||
<Button className="group relative w-full !bg-white !text-gray-600 !h-[46px] overflow-hidden !border !border-solid !border-[#d0d5dd]" disabled={!loaded || isLoading}>
|
||||
<div className="absolute left-0 top-0 w-full flex-center gap-3 z-[998] h-10 bg-inherit">
|
||||
<IconGoogle className="w-6 h-6" />
|
||||
{loadError
|
||||
? "Script Load Error!"
|
||||
: loaded
|
||||
? `${type === "login" ? t("login.google") : t("reg.google")}`
|
||||
: `Initializing`}
|
||||
</div>
|
||||
<div className="hide">
|
||||
<div className="absolute left-0 top-0 w-full group-hover:opacity-0 group-hover:z-[999]">
|
||||
<GoogleLogin
|
||||
width="360px"
|
||||
onSuccess={(res) => {
|
||||
@@ -107,7 +65,7 @@ const GoogleLoginInner: FC<Props> = ({ type = "login", loaded, loadError }) => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</StyledSocialButton>
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,51 +1,8 @@
|
||||
import { FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import Modal from "../Modal";
|
||||
import IconClose from "../../../assets/icons/close.svg";
|
||||
import Button from "../styled/Button";
|
||||
|
||||
const Styled = styled.div`
|
||||
position: relative;
|
||||
margin-top: 15px;
|
||||
pointer-events: all;
|
||||
width: 406px;
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
background: #fff;
|
||||
box-shadow: 0 25px 50px rgba(31, 41, 55, 0.25);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
.tip {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
color: #344054;
|
||||
.title {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
.desc {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
.btns {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 16px;
|
||||
}
|
||||
.close {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
handleInstall?: () => void;
|
||||
closePrompt?: () => void;
|
||||
@@ -54,13 +11,13 @@ interface Props {
|
||||
const Prompt: FC<Props> = ({ handleInstall, closePrompt }) => {
|
||||
return (
|
||||
<Modal mask={false}>
|
||||
<Styled>
|
||||
<IconClose className="close" onClick={closePrompt} />
|
||||
<div className="tip">
|
||||
<h2 className="title">Install web app on desktop?</h2>
|
||||
<p className="desc">Add to desktop for quick access to this app.</p>
|
||||
<div className="relative pointer-events-auto mt-4 w-[406px] p-4 rounded-md bg-white shadow-md flex flex-col gap-3">
|
||||
<IconClose className="absolute top-4 right-4 cursor-pointer" onClick={closePrompt} />
|
||||
<div className="flex flex-col gap-4 text-gray-600">
|
||||
<h2 className="font-semibold">Install web app on desktop?</h2>
|
||||
<p className="text-sm">Add to desktop for quick access to this app.</p>
|
||||
</div>
|
||||
<div className="btns">
|
||||
<div className="w-full flex justify-end gap-4">
|
||||
<Button className="ghost cancel small" onClick={closePrompt}>
|
||||
Cancel
|
||||
</Button>
|
||||
@@ -68,7 +25,7 @@ const Prompt: FC<Props> = ({ handleInstall, closePrompt }) => {
|
||||
Install
|
||||
</Button>
|
||||
</div>
|
||||
</Styled>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,21 +3,10 @@ import "prismjs/themes/prism.css";
|
||||
import "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css";
|
||||
//@ts-ignore
|
||||
import codeSyntaxHighlight from "@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight-all.js";
|
||||
|
||||
import { Viewer } from "@toast-ui/react-editor";
|
||||
import styled from "styled-components";
|
||||
|
||||
import ImagePreviewModal, { PreviewImageData } from "./ImagePreviewModal";
|
||||
|
||||
const Styled = styled.div`
|
||||
* {
|
||||
user-select: text;
|
||||
}
|
||||
.toastui-editor-contents {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
`;
|
||||
|
||||
interface IProps {
|
||||
content: string;
|
||||
@@ -60,9 +49,9 @@ const MarkdownRender: FC<IProps> = ({ content }) => {
|
||||
{previewImage && (
|
||||
<ImagePreviewModal download={false} data={previewImage} closeModal={closePreviewModal} />
|
||||
)}
|
||||
<Styled ref={mdContainer}>
|
||||
<div ref={mdContainer} id="MARKDOWN_CONTAINER">
|
||||
<Viewer initialValue={content} plugins={[codeSyntaxHighlight]}></Viewer>
|
||||
</Styled>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,43 +1,18 @@
|
||||
// import React from 'react'
|
||||
import Tippy, { TippyProps } from "@tippyjs/react";
|
||||
import styled from "styled-components";
|
||||
import { FC } from "react";
|
||||
import Tippy, { TippyProps } from "@tippyjs/react";
|
||||
import clsx from "clsx";
|
||||
|
||||
const StyledTip = styled.div`
|
||||
position: relative;
|
||||
background: #fff;
|
||||
padding: 8px 12px;
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #1d2939;
|
||||
border-radius: var(--br);
|
||||
box-shadow: 0 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03);
|
||||
&::after {
|
||||
background-color: inherit;
|
||||
position: absolute;
|
||||
content: "";
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 1px;
|
||||
transform-origin: center;
|
||||
}
|
||||
&.right::after {
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translate3d(-50%, -50%, 0) rotate(45deg);
|
||||
}
|
||||
&.top::after {
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
transform: translate3d(-50%, 50%, 0) rotate(45deg);
|
||||
}
|
||||
&.bottom::after {
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate3d(-50%, -50%, 0) rotate(45deg);
|
||||
}
|
||||
`;
|
||||
|
||||
const Triangle: FC<Pick<TippyProps, "placement">> = ({ placement }) => {
|
||||
if (placement == "left") return null;
|
||||
const cls = clsx("w-3 h-3 bg-inherit absolute rounded-[1px] origin-center rotate-45",
|
||||
placement == "right" && "left-0 top-1/2 -translate-x-1/2 -translate-y-1/2",
|
||||
placement == "top" && "left-1/2 bottom-0 -translate-x-1/2 translate-y-1/2",
|
||||
placement == "bottom" && "top-0 left-1/2 -translate-x-1/2 -translate-y-1/2",
|
||||
|
||||
);
|
||||
return <i className={cls}></i>;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
tip: string;
|
||||
@@ -52,7 +27,10 @@ const Tooltip: FC<Props> = ({ tip = "", placement = "right", delay = null, child
|
||||
duration={delay ? defaultDuration : 0}
|
||||
delay={delay ?? [150, 0]}
|
||||
placement={placement}
|
||||
content={<StyledTip className={placement}>{tip}</StyledTip>}
|
||||
content={<div className="relative bg-white px-3 py-2 text-xs rounded-lg drop-shadow text-gray-700">
|
||||
<Triangle placement={placement} />
|
||||
{tip}
|
||||
</div>}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -6,7 +6,6 @@ import IconBot from "../../../assets/icons/bot.svg";
|
||||
import Avatar from "../Avatar";
|
||||
import Profile from "../Profile";
|
||||
import ContextMenu from "./ContextMenu";
|
||||
import StyledWrapper from "./styled";
|
||||
import useContextMenu from "../../hook/useContextMenu";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import clsx from "clsx";
|
||||
@@ -44,6 +43,11 @@ 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 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");
|
||||
if (!popover)
|
||||
return (
|
||||
<ContextMenu
|
||||
@@ -53,30 +57,30 @@ const User: FC<Props> = ({
|
||||
visible={contextMenuVisible}
|
||||
hide={hideContextMenu}
|
||||
>
|
||||
<StyledWrapper
|
||||
size={avatarSize}
|
||||
className={`${interactive ? "interactive" : ""} ${compact ? "compact" : ""} relative`}
|
||||
<div
|
||||
className={containerClass}
|
||||
onDoubleClick={dm ? handleDoubleClick : undefined}
|
||||
onContextMenu={enableContextMenu ? handleContextMenuEvent : undefined}
|
||||
>
|
||||
<div className="avatar">
|
||||
<div className="cursor-pointer relative" style={{ width: `${avatarSize}px`, height: `${avatarSize}px` }}>
|
||||
<Avatar
|
||||
className="w-full h-full rounded-full object-cover"
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
src={curr.avatar}
|
||||
name={curr.name}
|
||||
alt="avatar"
|
||||
/>
|
||||
<div className={`status ${online ? "online" : "offline"}`}></div>
|
||||
<div className={statusClass}></div>
|
||||
</div>
|
||||
{!compact && (
|
||||
<span className="name" title={curr?.name}>
|
||||
<span className={nameClass} title={curr?.name}>
|
||||
{curr?.name}
|
||||
</span>
|
||||
)}
|
||||
{owner && <IconOwner />}
|
||||
{curr.is_bot && <IconBot className={clsx(compact && "absolute -top-1 -right-1", "!w-4 !h-4")} />}
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
</ContextMenu>
|
||||
);
|
||||
return (
|
||||
@@ -94,30 +98,30 @@ const User: FC<Props> = ({
|
||||
trigger="click"
|
||||
content={<Profile uid={uid} type="card" cid={cid} />}
|
||||
>
|
||||
<StyledWrapper
|
||||
size={avatarSize}
|
||||
className={`${interactive ? "interactive" : ""} ${compact ? "compact" : ""}`}
|
||||
<div
|
||||
className={containerClass}
|
||||
onDoubleClick={dm ? handleDoubleClick : undefined}
|
||||
onContextMenu={enableContextMenu ? handleContextMenuEvent : undefined}
|
||||
>
|
||||
<div className="avatar">
|
||||
<div className="cursor-pointer relative" style={{ width: `${avatarSize}px`, height: `${avatarSize}px` }}>
|
||||
<Avatar
|
||||
className="w-full h-full rounded-full object-cover"
|
||||
width={avatarSize}
|
||||
height={avatarSize}
|
||||
src={curr.avatar}
|
||||
name={curr.name}
|
||||
alt="avatar"
|
||||
/>
|
||||
<div className={`status ${online ? "online" : "offline"}`}></div>
|
||||
<div className={statusClass}></div>
|
||||
</div>
|
||||
{!compact && (
|
||||
<span className="name" title={curr?.name}>
|
||||
<span className={nameClass} title={curr?.name}>
|
||||
{curr?.name}
|
||||
</span>
|
||||
)}
|
||||
{owner && <IconOwner />}
|
||||
{curr.is_bot && <IconBot className="!w-4 !h-4" />}
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
</Tippy>
|
||||
</ContextMenu>
|
||||
);
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
border-radius: 8px;
|
||||
user-select: none;
|
||||
&.interactive {
|
||||
&:hover,
|
||||
&.active {
|
||||
background: rgba(116, 127, 141, 0.1);
|
||||
}
|
||||
}
|
||||
.avatar {
|
||||
cursor: pointer;
|
||||
width: ${({ size }: { size: number }) => `${size}px`};
|
||||
height: ${({ size }: { size: number }) => `${size}px`};
|
||||
position: relative;
|
||||
img {
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.status {
|
||||
position: absolute;
|
||||
bottom: -2px;
|
||||
right: -6px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
box-sizing: content-box;
|
||||
border-radius: 50%;
|
||||
border: 2px solid #fff;
|
||||
&.online {
|
||||
background-color: #22c55e;
|
||||
}
|
||||
&.offline {
|
||||
background-color: #a1a1aa;
|
||||
}
|
||||
&.alert {
|
||||
background-color: #dc2626;
|
||||
}
|
||||
}
|
||||
}
|
||||
.name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #52525b;
|
||||
max-width: 190px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
/* session nav */
|
||||
&.compact {
|
||||
padding: 0;
|
||||
.avatar .status {
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -160,7 +160,7 @@ export function sliceFile(file: File | null, chunksAmount: number) {
|
||||
|
||||
return chunks;
|
||||
}
|
||||
export const getFileIcon = (type: string, name = "") => {
|
||||
export const getFileIcon = (type: string, name = "", className = "icon") => {
|
||||
let icon = null;
|
||||
|
||||
const checks = {
|
||||
@@ -176,27 +176,27 @@ export const getFileIcon = (type: string, name = "") => {
|
||||
switch (true) {
|
||||
case checks.image.test(_type):
|
||||
{
|
||||
icon = <IconImage className="icon" />;
|
||||
icon = <IconImage className={className} />;
|
||||
}
|
||||
break;
|
||||
case checks.pdf.test(_type):
|
||||
icon = <IconPdf className="icon" />;
|
||||
icon = <IconPdf className={className} />;
|
||||
break;
|
||||
case checks.code.test(_type):
|
||||
icon = <IconCode className="icon" />;
|
||||
icon = <IconCode className={className} />;
|
||||
break;
|
||||
case checks.doc.test(_type):
|
||||
icon = <IconDoc className="icon" />;
|
||||
icon = <IconDoc className={className} />;
|
||||
break;
|
||||
case checks.audio.test(_type):
|
||||
icon = <IconAudio className="icon" />;
|
||||
icon = <IconAudio className={className} />;
|
||||
break;
|
||||
case checks.video.test(_type):
|
||||
icon = <IconVideo className="icon" />;
|
||||
icon = <IconVideo className={className} />;
|
||||
break;
|
||||
|
||||
default:
|
||||
icon = <IconUnknown className="icon" />;
|
||||
icon = <IconUnknown className={className} />;
|
||||
break;
|
||||
}
|
||||
return icon;
|
||||
|
||||
@@ -6,74 +6,13 @@ import IconClose from "../../../assets/icons/close.svg";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PinnedMessage from "../../../common/component/PinnedMessage";
|
||||
const Styled = styled.div`
|
||||
padding: 16px;
|
||||
background: #f9fafb;
|
||||
filter: drop-shadow(0px 25px 50px rgba(31, 41, 55, 0.25));
|
||||
border-radius: 12px;
|
||||
min-width: 486px;
|
||||
max-height: 90vh;
|
||||
overflow-y: scroll;
|
||||
/* width: fit-content; */
|
||||
> .head {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #344054;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
> .none {
|
||||
padding: 16px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
.tip {
|
||||
width: 240px;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
color: #475467;
|
||||
}
|
||||
}
|
||||
> .list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
.pin {
|
||||
position: relative;
|
||||
border: 1px solid #f2f4f7;
|
||||
border-radius: var(--br);
|
||||
> .preview {
|
||||
.preview {
|
||||
background: none;
|
||||
.down img {
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
}
|
||||
}
|
||||
> .opts {
|
||||
visibility: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
padding: 4px;
|
||||
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||
border-radius: 6px;
|
||||
.btn {
|
||||
display: flex;
|
||||
background: none;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
&:hover .opts {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
type Props = {
|
||||
id: number;
|
||||
@@ -88,22 +27,22 @@ const PinList: FC<Props> = ({ id }: Props) => {
|
||||
};
|
||||
const noPins = pins.length == 0;
|
||||
return (
|
||||
<Styled>
|
||||
<h4 className="head">{t("pinned_msg")}({pins.length})</h4>
|
||||
<Styled className="p-4 drop-shadow-md overflow-y-scroll max-h-[90vh] min-w-[486px] rounded-xl bg-gray-50">
|
||||
<h4 className=" text-gray-600 mb-4 font-semibold">{t("pinned_msg")}({pins.length})</h4>
|
||||
{noPins ? (
|
||||
<div className="none">
|
||||
<div className="flex flex-col items-center gap-2 w-full p-4">
|
||||
<IconSurprise />
|
||||
<div className="tip">{t("pin_empty_tip")}</div>
|
||||
<div className="w-60 font-semibold text-gray-500 text-center">{t("pin_empty_tip")}</div>
|
||||
</div>
|
||||
) : (
|
||||
<ul className="list">
|
||||
<ul className="flex flex-col gap-2">
|
||||
{pins.map((data) => {
|
||||
return (
|
||||
<li key={data.mid} className="pin">
|
||||
<li key={data.mid} className="group relative border border-solid border-slate-100 rounded-md ">
|
||||
<PinnedMessage data={data} />
|
||||
<div className="opts">
|
||||
<div className="invisible group-hover:visible flex items-center gap-1 absolute top-1 right-1 p-1 border border-solid border-black/10 rounded-md">
|
||||
{canPin && (
|
||||
<button className="btn" data-mid={data.mid} onClick={handleUnpin}>
|
||||
<button className="flex bg-none border-none" data-mid={data.mid} onClick={handleUnpin}>
|
||||
<IconClose className="fill-slate-900" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -21,7 +21,7 @@ import IconPin from "../../../assets/icons/pin.svg";
|
||||
import IconHeadphone from "../../../assets/icons/headphone.svg";
|
||||
|
||||
import addIcon from "../../../assets/icons/add.svg?url";
|
||||
import { StyledUsers, StyledChannelChat, StyledHeader } from "./styled";
|
||||
import { StyledUsers, StyledChannelChat } from "./styled";
|
||||
import InviteModal from "../../../common/component/InviteModal";
|
||||
import LoadMore from "../LoadMore";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
@@ -142,13 +142,13 @@ function ChannelChat({ cid = 0, dropFiles = [] }: Props) {
|
||||
</ul>
|
||||
}
|
||||
header={
|
||||
<StyledHeader className="head">
|
||||
<div className="txt">
|
||||
<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="title">{name}</span>
|
||||
<span className="desc">{description}</span>
|
||||
<span className="text-gray-800">{name}</span>
|
||||
<span className="ml-2 text-gray-500">{description}</span>
|
||||
</div>
|
||||
</StyledHeader>
|
||||
</header>
|
||||
}
|
||||
users={
|
||||
<StyledUsers className={membersVisible ? "flex" : "hidden"}>
|
||||
|
||||
@@ -1,27 +1,5 @@
|
||||
import styled from "styled-components";
|
||||
export const StyledHeader = styled.header`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.txt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
.title {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #1c1c1e;
|
||||
}
|
||||
.desc {
|
||||
margin-left: 8px;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #616161;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledNotification = styled.div`
|
||||
padding: 3px 8px;
|
||||
font-style: normal;
|
||||
|
||||
@@ -7,7 +7,6 @@ import FavIcon from "../../../assets/icons/bookmark.svg";
|
||||
import { useReadMessageMutation } from "../../../app/services/message";
|
||||
import User from "../../../common/component/User";
|
||||
import Layout from "../Layout";
|
||||
import { StyledHeader, StyledDMChat } from "./styled";
|
||||
import LoadMore from "../LoadMore";
|
||||
import { renderMessageFragment } from "../utils";
|
||||
import useMessageFeed from "../../../common/hook/useMessageFeed";
|
||||
@@ -65,12 +64,12 @@ const DMChat: FC<Props> = ({ uid = 0, dropFiles }) => {
|
||||
</ul>
|
||||
}
|
||||
header={
|
||||
<StyledHeader className="head">
|
||||
<header className="head h-full flex items-center justify-between">
|
||||
<User interactive={false} uid={currUser.uid} />
|
||||
</StyledHeader>
|
||||
</header>
|
||||
}
|
||||
>
|
||||
<StyledDMChat id={`VOCECHAT_FEED_user_${uid}`}>
|
||||
<article id={`VOCECHAT_FEED_user_${uid}`} className="w-full h-full px-4 py-4.5 overflow-auto">
|
||||
{hasMore ? <LoadMore pullUp={pullUp} pulling={pulling} /> : null}
|
||||
{[...feeds].map((mid, idx) => {
|
||||
const curr = messageData[mid];
|
||||
@@ -86,7 +85,7 @@ const DMChat: FC<Props> = ({ uid = 0, dropFiles }) => {
|
||||
context: "user"
|
||||
});
|
||||
})}
|
||||
</StyledDMChat>
|
||||
</article>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import styled from "styled-components";
|
||||
export const StyledHeader = styled.header`
|
||||
height: 100%;
|
||||
/* padding: 0 20px 0 10px; */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
/* tricky */
|
||||
> div {
|
||||
padding-left: 4px;
|
||||
}
|
||||
.txt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
.title {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #1c1c1e;
|
||||
}
|
||||
.desc {
|
||||
margin-left: 8px;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #616161;
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const StyledDMChat = styled.article`
|
||||
width: 100%;
|
||||
padding: 18px 16px;
|
||||
height: 100%;
|
||||
height: -webkit-fill-available;
|
||||
overflow: auto;
|
||||
`;
|
||||
@@ -4,7 +4,7 @@ import useMessageFeed from "../../../common/hook/useMessageFeed";
|
||||
import ChannelIcon from "../../../common/component/ChannelIcon";
|
||||
import Layout from "../Layout";
|
||||
import { renderMessageFragment } from "../utils";
|
||||
import { StyledChannelChat, StyledHeader } from "./styled";
|
||||
import { StyledChannelChat } from "./styled";
|
||||
import LoadMore from "../LoadMore";
|
||||
import { useAppSelector } from "../../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -40,13 +40,13 @@ export default function GuestChannelChat({ cid = 0 }: Props) {
|
||||
to={cid}
|
||||
context="channel"
|
||||
header={
|
||||
<StyledHeader className="head">
|
||||
<div className="txt">
|
||||
<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="title">{name}</span>
|
||||
<span className="desc">{description}</span>
|
||||
<span className="text-gray-800">{name}</span>
|
||||
<span className="ml-2 text-gray-500">{description}</span>
|
||||
</div>
|
||||
</StyledHeader>
|
||||
</header>
|
||||
}
|
||||
>
|
||||
<StyledChannelChat id={`VOCECHAT_FEED_channel_${cid}`}>
|
||||
|
||||
@@ -1,27 +1,4 @@
|
||||
import styled from "styled-components";
|
||||
export const StyledHeader = styled.header`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.txt {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
.title {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #1c1c1e;
|
||||
}
|
||||
.desc {
|
||||
margin-left: 8px;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #616161;
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const StyledChannelChat = styled.article`
|
||||
padding: 18px 16px;
|
||||
width: 100%;
|
||||
|
||||
@@ -14,6 +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';
|
||||
function ChatPage() {
|
||||
const [sessionListVisible, setSessionListVisible] = useState(false);
|
||||
const [channelModalVisible, setChannelModalVisible] = useState(false);
|
||||
@@ -51,13 +52,14 @@ function ChatPage() {
|
||||
<ChannelModal closeModal={toggleChannelModalVisible} personal={true} />
|
||||
)}
|
||||
{usersModalVisible && <UsersModal closeModal={toggleUsersModalVisible} />}
|
||||
<StyledWrapper className={`${isGuest ? "!pr-1" : ""} 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")}>
|
||||
<Server readonly={isGuest} />
|
||||
{isGuest ? <GuestSessionList /> : <SessionList tempSession={tmpSession} />}
|
||||
<button className="absolute top-1/2 -right-[24px] z-50 p-2 rounded-full bg-slate-300/80 md:hidden" onClick={toggleSessionList}>
|
||||
{sessionListVisible ? null : <button className="absolute top-2 -right-[52px] z-50 p-2 bg-none md:hidden" onClick={toggleSessionList}>
|
||||
<IconList />
|
||||
</button>
|
||||
</button>}
|
||||
</div>
|
||||
<div className={`right ${placeholderVisible ? "placeholder" : ""}`}>
|
||||
{placeholderVisible && (isGuest ? <GuestBlankPlaceholder /> : <BlankPlaceholder />)}
|
||||
|
||||
@@ -1,23 +1,8 @@
|
||||
import { FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import { NavLink, useLocation } from "react-router-dom";
|
||||
import Avatar from "../../common/component/Avatar";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
padding: 10px 12px;
|
||||
.avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
img {
|
||||
object-fit: cover;
|
||||
border-radius: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
uid: number;
|
||||
}
|
||||
@@ -28,13 +13,13 @@ const User: FC<Props> = ({ uid }) => {
|
||||
if (!user) return null;
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="px-3 py-2.5 invisible md:visible">
|
||||
<NavLink to={`/setting?nav=my_account&f=${pathname}`}>
|
||||
<div className="avatar">
|
||||
<Avatar width={32} height={32} src={user.avatar} name={user.name} />
|
||||
<div className="w-8 h-8">
|
||||
<Avatar className=" object-cover w-full h-full" width={32} height={32} src={user.avatar} name={user.name} />
|
||||
</div>
|
||||
</NavLink>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import styled from "styled-components";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import Button from "../../common/component/styled/Button";
|
||||
|
||||
const StyledMagicButton = styled(Button)`
|
||||
width: 100%;
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
export default function MagicLinkLogin() {
|
||||
const navigate = useNavigate();
|
||||
const handleGoogleLogin = () => {
|
||||
@@ -14,5 +8,5 @@ export default function MagicLinkLogin() {
|
||||
// signIn();
|
||||
};
|
||||
|
||||
return <StyledMagicButton onClick={handleGoogleLogin}>Sign in with Magic Link</StyledMagicButton>;
|
||||
return <Button className="w-full mb-4" onClick={handleGoogleLogin}>Sign in with Magic Link</Button>;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useState, FC, useRef } from "react";
|
||||
import styled from "styled-components";
|
||||
import toast from "react-hot-toast";
|
||||
import { useDispatch } from "react-redux";
|
||||
import StyledInput from "../../../common/component/styled/Input";
|
||||
@@ -15,53 +14,6 @@ import { useAppSelector } from "../../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useWizard } from "react-use-wizard";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> .primaryText {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
> .secondaryText {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
form {
|
||||
> .input {
|
||||
margin-bottom: 20px;
|
||||
width: 360px;
|
||||
height: 44px;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid #d0d5dd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
|
||||
> .inner {
|
||||
padding: 0;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}}
|
||||
|
||||
> .button {
|
||||
width: 360px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
`;
|
||||
type Props = {
|
||||
serverName: string;
|
||||
};
|
||||
@@ -122,12 +74,12 @@ const AdminAccount: FC<Props> = ({ serverName }) => {
|
||||
}, [isUpdatedServer]);
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<span className="primaryText">{t("admin_title")}</span>
|
||||
<span className="secondaryText">{t("admin_desc")}</span>
|
||||
<form ref={formRef} action="/">
|
||||
<div className="h-full flex-center flex-col text-center w-[360px] m-auto">
|
||||
<span className="text-2xl mb-2 font-bold">{t("admin_title")}</span>
|
||||
<span className="text-sm mb-6">{t("admin_desc")}</span>
|
||||
<form ref={formRef} action="/" className="flex flex-col gap-2 w-full">
|
||||
<StyledInput
|
||||
className="input"
|
||||
className="large"
|
||||
placeholder="Enter your email"
|
||||
type={"email"}
|
||||
required
|
||||
@@ -135,7 +87,7 @@ const AdminAccount: FC<Props> = ({ serverName }) => {
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
<StyledInput
|
||||
className="input"
|
||||
className="large"
|
||||
type="password"
|
||||
required
|
||||
minLength={6}
|
||||
@@ -144,7 +96,7 @@ const AdminAccount: FC<Props> = ({ serverName }) => {
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
/>
|
||||
<StyledInput
|
||||
className="input"
|
||||
className="large"
|
||||
type="password"
|
||||
required
|
||||
minLength={6}
|
||||
@@ -154,7 +106,7 @@ const AdminAccount: FC<Props> = ({ serverName }) => {
|
||||
/>
|
||||
</form>
|
||||
<StyledButton
|
||||
className="button"
|
||||
className="mt-6 w-full"
|
||||
onClick={async () => {
|
||||
const formEle = formRef?.current;
|
||||
if (formEle) {
|
||||
@@ -174,7 +126,7 @@ const AdminAccount: FC<Props> = ({ serverName }) => {
|
||||
>
|
||||
{!(isSigningUp || isLoggingIn || isUpdatingServer) ? t("sign") : "..."}
|
||||
</StyledButton>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default AdminAccount;
|
||||
|
||||
@@ -1,80 +1,26 @@
|
||||
import styled from "styled-components";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import StyledButton from "../../../common/component/styled/Button";
|
||||
import PlayIcon from "../../../assets/icons/play.svg?url";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> .primaryText {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
> .secondaryText {
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
> .tip {
|
||||
width: 588px;
|
||||
font-size: 20px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
margin-bottom: 48px;
|
||||
|
||||
> .strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
> .startButton {
|
||||
width: 128px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 15px 0 12px;
|
||||
|
||||
> img {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
> span {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function DonePage({ serverName }: { serverName: string }) {
|
||||
const { t } = useTranslation("welcome", { keyPrefix: "onboarding" });
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<span className="primaryText">{t("done_welcome", { serverName })}</span>
|
||||
<span className="secondaryText">{t("done_title")}</span>
|
||||
<span className="tip">
|
||||
<div className="h-full flex-center flex-col text-center w-[588px] m-auto">
|
||||
<span className="text-2xl font-bold mb-2">{t("done_welcome", { serverName })}</span>
|
||||
<span className="text-sm mb-12">{t("done_title")}</span>
|
||||
<span className="text-xl mb-12">
|
||||
<Trans i18nKey={"onboarding.done_desc"} ns={"welcome"}>
|
||||
<span className="strong" />
|
||||
<span className="font-bold" />
|
||||
</Trans>
|
||||
</span>
|
||||
<StyledButton className="startButton" onClick={() => navigate("/")}>
|
||||
<img src={PlayIcon} alt="play icon" />
|
||||
<span>{t("enter")}</span>
|
||||
<StyledButton className="!w-32 flex flex-col items-center py-3" onClick={() => navigate("/")}>
|
||||
<img className="mb-2" src={PlayIcon} alt="play icon" />
|
||||
<span className="text-sm">{t("enter")}</span>
|
||||
</StyledButton>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,52 +1,10 @@
|
||||
import { FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import toast from "react-hot-toast";
|
||||
import StyledInput from "../../../common/component/styled/Input";
|
||||
import StyledButton from "../../../common/component/styled/Button";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useWizard } from "react-use-wizard";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> .primaryText {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
> .secondaryText {
|
||||
width: 360px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 24px;
|
||||
color: #667085;
|
||||
}
|
||||
|
||||
> .input {
|
||||
width: 360px;
|
||||
height: 44px;
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid #d0d5dd;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05);
|
||||
}
|
||||
|
||||
> .button {
|
||||
width: 360px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
`;
|
||||
type Props = {
|
||||
serverName: string;
|
||||
setServerName: (name: string) => void;
|
||||
@@ -54,20 +12,21 @@ type Props = {
|
||||
const ServerName: FC<Props> = ({ serverName, setServerName }) => {
|
||||
const { t } = useTranslation("welcome", { keyPrefix: "onboarding" });
|
||||
const { nextStep } = useWizard();
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<span className="primaryText">{t("new_server")}</span>
|
||||
<span className="secondaryText">
|
||||
<div className="h-full flex-center flex-col text-center w-[360px] m-auto">
|
||||
<span className="text-2xl mb-2 font-bold">{t("new_server")}</span>
|
||||
<span className="text-sm mb-6 text-gray-400 ">
|
||||
{t("server_desc")}
|
||||
</span>
|
||||
<StyledInput
|
||||
className="input"
|
||||
className="h-11 px-3.5 py-2.5 border-gray-300 rounded-lg shadow"
|
||||
placeholder={t("placeholder_server")}
|
||||
value={serverName}
|
||||
onChange={(e) => setServerName(e.target.value)}
|
||||
/>
|
||||
<StyledButton
|
||||
className="button"
|
||||
className="w-full mt-6"
|
||||
onClick={() => {
|
||||
// Verification for space name
|
||||
if (serverName === "") {
|
||||
@@ -79,7 +38,7 @@ const ServerName: FC<Props> = ({ serverName, setServerName }) => {
|
||||
>
|
||||
{t("create_server")}
|
||||
</StyledButton>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default ServerName;
|
||||
|
||||
@@ -1,64 +1,22 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
import StyledButton from "../../../common/component/styled/Button";
|
||||
import PlayIcon from "../../../assets/icons/play.svg?url";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useWizard } from "react-use-wizard";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> .primaryText {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
> .secondaryText {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
> .startButton {
|
||||
width: 128px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 15px 0 12px;
|
||||
|
||||
> img {
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
> span {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function WelcomePage() {
|
||||
const { t } = useTranslation("welcome", { keyPrefix: "onboarding" });
|
||||
const { nextStep } = useWizard();
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<span className="primaryText">{t("welcome")}</span>
|
||||
<span className="secondaryText">
|
||||
<div className="flex-center flex-col h-full text-center">
|
||||
<span className="text-2xl mb-2 font-bold">{t("welcome")}</span>
|
||||
<span className="text-sm mb-6">
|
||||
{t("welcome_desc")}
|
||||
</span>
|
||||
<StyledButton className="startButton" onClick={nextStep}>
|
||||
<StyledButton className="!w-32 flex flex-col gap-2 items-center py-3 text-sm" onClick={nextStep}>
|
||||
<img src={PlayIcon} alt="play icon" />
|
||||
<span>{t("start")}</span>
|
||||
</StyledButton>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,39 +8,6 @@ import { WhoCanSignUp } from "../../../types/server";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useWizard } from "react-use-wizard";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
> .primaryText {
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 24px;
|
||||
line-height: 30px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
> .secondaryText {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
> form {
|
||||
width: 512px;
|
||||
}
|
||||
|
||||
> .button {
|
||||
width: 124px;
|
||||
height: 44px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function SignUpSetting() {
|
||||
const { t } = useTranslation("welcome");
|
||||
const { t: st } = useTranslation("setting");
|
||||
@@ -73,10 +40,22 @@ 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>
|
||||
<span className="primaryText">{t("onboarding.invite_title")}</span>
|
||||
<span className="secondaryText">{t("onboarding.invite_desc")}</span>
|
||||
<StyledWrapper 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
|
||||
options={[st("overview.sign_up.everyone"), st("overview.sign_up.invite")]}
|
||||
values={["EveryOne", "InvitationOnly"]}
|
||||
@@ -84,7 +63,7 @@ export default function SignUpSetting() {
|
||||
onChange={setValue}
|
||||
/>}
|
||||
<StyledButton
|
||||
className="button"
|
||||
className="w-32 mt-6"
|
||||
disabled={!value}
|
||||
onClick={() => {
|
||||
// nextStep();
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import styled from "styled-components";
|
||||
import IconPdf from "../../../assets/icons/file.pdf.svg";
|
||||
import IconAudio from "../../../assets/icons/file.audio.svg";
|
||||
import IconVideo from "../../../assets/icons/file.video.svg";
|
||||
@@ -9,73 +8,34 @@ import IconImage from "../../../assets/icons/file.image.svg";
|
||||
import CheckSign from "../../../assets/icons/check.sign.svg";
|
||||
import { FC } from "react";
|
||||
|
||||
const Styled = styled.div`
|
||||
padding: 12px;
|
||||
background: #ffffff;
|
||||
min-width: 200px;
|
||||
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;
|
||||
> .list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
.type {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #475467;
|
||||
.icon {
|
||||
width: 15px;
|
||||
height: auto;
|
||||
}
|
||||
.check {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const FileTypes = {
|
||||
doc: {
|
||||
title: "Documents",
|
||||
icon: <IconDoc className="icon" />
|
||||
icon: <IconDoc className="w-4 h-auto" />
|
||||
},
|
||||
pdf: {
|
||||
title: "PDFs",
|
||||
icon: <IconPdf className="icon" />
|
||||
icon: <IconPdf className="w-4 h-auto" />
|
||||
},
|
||||
image: {
|
||||
title: "Images",
|
||||
icon: <IconImage className="icon" />
|
||||
icon: <IconImage className="w-4 h-auto" />
|
||||
},
|
||||
audio: {
|
||||
title: "Audio",
|
||||
icon: <IconAudio className="icon" />
|
||||
icon: <IconAudio className="w-4 h-auto" />
|
||||
},
|
||||
video: {
|
||||
title: "Videos",
|
||||
icon: <IconVideo className="icon" />
|
||||
icon: <IconVideo className="w-4 h-auto" />
|
||||
},
|
||||
code: {
|
||||
title: "Code Snippets",
|
||||
icon: <IconCode className="icon" />
|
||||
icon: <IconCode className="w-4 h-auto" />
|
||||
},
|
||||
unknown: {
|
||||
title: "Unknown Files",
|
||||
icon: <IconUnknown className="icon" />
|
||||
icon: <IconUnknown className="w-4 h-auto" />
|
||||
}
|
||||
};
|
||||
type Props = {
|
||||
@@ -86,23 +46,24 @@ const Type: FC<Props> = ({ select = "", updateFilter }) => {
|
||||
const handleClick = (type?: string) => {
|
||||
updateFilter({ type });
|
||||
};
|
||||
|
||||
return (
|
||||
<Styled>
|
||||
<ul className="list">
|
||||
<li className="type" onClick={handleClick.bind(null, undefined)}>
|
||||
<div className="p-3 bg-white min-w-[100px] 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)}>
|
||||
Any Type
|
||||
{!select && <CheckSign className="check" />}
|
||||
</li>
|
||||
{Object.entries(FileTypes).map(([type, { title, icon }]) => {
|
||||
return (
|
||||
<li key={title} className="type" onClick={handleClick.bind(null, type)}>
|
||||
<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="check" />}
|
||||
{select == type && <CheckSign className="absolute right-0 left-1/2 -translate-y-1/2" />}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</Styled>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Type;
|
||||
|
||||
@@ -10,9 +10,9 @@ interface Props {
|
||||
const SentTip: FC<Props> = ({ email, reset }) => {
|
||||
const { t } = useTranslation("auth");
|
||||
return (
|
||||
<div className="tips">
|
||||
<h2 className="title">{t("check_email")}</h2>
|
||||
<span className="desc">
|
||||
<div className="flex flex-col items-center">
|
||||
<h2 className="font-semibold text-2xl text-gray-800 mb-2">{t("check_email")}</h2>
|
||||
<span className="text-center text-gray-500 mb-6">
|
||||
{t("check_email_desc", { email })}
|
||||
</span>
|
||||
<Button onClick={reset} className="main flex">
|
||||
|
||||
@@ -2,13 +2,13 @@ import { useState, useEffect, ChangeEvent, FormEvent } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import toast from "react-hot-toast";
|
||||
import BASE_URL from "../../app/config";
|
||||
import StyledWrapper from "./styled";
|
||||
import Input from "../../common/component/styled/Input";
|
||||
import Button from "../../common/component/styled/Button";
|
||||
import { useSendLoginMagicLinkMutation } from "../../app/services/auth";
|
||||
import SentTip from "./SentTip";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SocialLoginButtons from "../login/SocialLoginButtons";
|
||||
import Divider from "../../common/component/Divider";
|
||||
|
||||
export default function SendMagicLinkPage() {
|
||||
const { t } = useTranslation("auth");
|
||||
@@ -62,20 +62,19 @@ export default function SendMagicLinkPage() {
|
||||
setEmail("");
|
||||
setSent(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="form">
|
||||
<div className="flex-center h-screen">
|
||||
<div className="py-8 px-10 shadow rounded-xl">
|
||||
{sent ? (
|
||||
<SentTip email={email} reset={handleReset} />
|
||||
) : (
|
||||
<>
|
||||
<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("placeholder_email")}</span>
|
||||
<div className="flex flex-col items-center">
|
||||
<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 mb-2">{t("login.title")}</h2>
|
||||
<span className="text-center text-gray-500 mb-6">{t("placeholder_email")}</span>
|
||||
</div>
|
||||
<form onSubmit={handleLogin}>
|
||||
<form onSubmit={handleLogin} className="flex flex-col gap-5 w-[360px]">
|
||||
<Input
|
||||
type="email"
|
||||
className="large"
|
||||
@@ -91,14 +90,14 @@ export default function SendMagicLinkPage() {
|
||||
{isLoading ? "Sending" : t("continue")}
|
||||
</Button>
|
||||
</form>
|
||||
<hr className="or" />
|
||||
<Divider content="or" />
|
||||
<div className="flex flex-col gap-3 py-3">
|
||||
<SocialLoginButtons />
|
||||
</div>
|
||||
<Button onClick={handlePwdPath}>{t("login.password")}</Button>
|
||||
<Button onClick={handlePwdPath} className="flex">{t("login.password")}</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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: 32px 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;
|
||||
.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 {
|
||||
text-align: center;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #667085;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledWrapper;
|
||||
@@ -44,7 +44,7 @@ const LicensePriceListModal: FC<Props> = ({ closeModal }) => {
|
||||
const { t } = useTranslation("setting");
|
||||
const { t: ct } = useTranslation();
|
||||
const [getUrl, { isLoading, isSuccess }] = useGetLicensePaymentUrlMutation();
|
||||
const [host, setHost] = useState(location.host);
|
||||
const [host, setHost] = useState(location.hostname);
|
||||
const [popUpVisible, setPopUpVisible] = useState(false);
|
||||
const [selectPrice, setSelectPrice] = useState(
|
||||
`${LicensePriceList[0].pid}|${LicensePriceList[0].limit}|${LicensePriceList[0].type}|${LicensePriceList[0].sub_dur || ""}`
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ChangeEvent, FC, useEffect, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import toast from "react-hot-toast";
|
||||
import Modal from "../../common/component/Modal";
|
||||
import StyledModal from "../../common/component/styled/Modal";
|
||||
@@ -8,25 +7,6 @@ import Checkbox from "../../common/component/styled/Checkbox";
|
||||
import useLogout from "../../common/hook/useLogout";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledConfirm = styled(StyledModal)`
|
||||
.clear {
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #6b7280;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
.txt {
|
||||
cursor: pointer;
|
||||
color: orange;
|
||||
margin-right: 12px;
|
||||
}
|
||||
input {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
closeModal: () => void;
|
||||
@@ -56,7 +36,7 @@ const LogoutConfirmModal: FC<Props> = ({ closeModal }) => {
|
||||
|
||||
return (
|
||||
<Modal id="modal-modal">
|
||||
<StyledConfirm
|
||||
<StyledModal
|
||||
title={t("logout.title")}
|
||||
description={t("logout.desc")}
|
||||
buttons={
|
||||
@@ -68,13 +48,13 @@ const LogoutConfirmModal: FC<Props> = ({ closeModal }) => {
|
||||
</>
|
||||
}
|
||||
>
|
||||
<div className="clear">
|
||||
<label htmlFor="clear_cb" className="txt">
|
||||
<div className="text-sm text-gray-400 flex justify-end items-center">
|
||||
<label htmlFor="clear_cb" className="cursor-pointer text-orange-500 mr-3">
|
||||
{t("logout.clear_local")}
|
||||
</label>
|
||||
<Checkbox name="clear_cb" checked={clearLocal} onChange={handleCheck} />
|
||||
<Checkbox className=" cursor-pointer" name="clear_cb" checked={clearLocal} onChange={handleCheck} />
|
||||
</div>
|
||||
</StyledConfirm>
|
||||
</StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,105 +1,14 @@
|
||||
import { useEffect, useState, MouseEvent } from "react";
|
||||
import styled from "styled-components";
|
||||
import toast from "react-hot-toast";
|
||||
import { useUpdateAvatarMutation } from "../../app/services/user";
|
||||
import AvatarUploader from "../../common/component/AvatarUploader";
|
||||
import Button from "../../common/component/styled/Button";
|
||||
import ProfileBasicEditModal from "./ProfileBasicEditModal";
|
||||
import RemoveAccountConfirmModal from "./RemoveAccountConfirmModal";
|
||||
import UpdatePasswordModal from "./UpdatePasswordModal";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32px;
|
||||
.card {
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 512px;
|
||||
background: #f3f4f6;
|
||||
border-radius: 20px;
|
||||
.name {
|
||||
margin-top: 8px;
|
||||
margin-bottom: 64px;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
line-height: 28px;
|
||||
color: #27272a;
|
||||
.uid {
|
||||
font-weight: normal;
|
||||
color: #52525b;
|
||||
}
|
||||
}
|
||||
.row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 24px;
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.label {
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
line-height: 20px;
|
||||
text-transform: uppercase;
|
||||
color: #52525b;
|
||||
}
|
||||
.txt {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #52525b;
|
||||
.gray {
|
||||
color: #78787c;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
background: #1fe1f9;
|
||||
border: 1px solid #1fe1f9;
|
||||
}
|
||||
}
|
||||
}
|
||||
.danger {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
.head {
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #374151;
|
||||
}
|
||||
.desc {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
color: #616161;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.btn {
|
||||
background: #ef4444;
|
||||
border: 1px solid #ef4444;
|
||||
}
|
||||
}
|
||||
.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: 0px 1px 2px rgba(16, 24, 40, 0.05);
|
||||
border-radius: 8px;
|
||||
}
|
||||
`;
|
||||
type EditField = "name" | "email" | "";
|
||||
export default function MyAccount() {
|
||||
const { t } = useTranslation("member");
|
||||
@@ -151,51 +60,49 @@ export default function MyAccount() {
|
||||
const { uid, avatar, name, email } = loginUser;
|
||||
return (
|
||||
<>
|
||||
<StyledWrapper>
|
||||
<div className="card">
|
||||
<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">
|
||||
<AvatarUploader url={avatar} name={name} uploadImage={uploadAvatar} />
|
||||
<div className="name">
|
||||
{name} <span className="uid">#{uid}</span>
|
||||
<div className="mt-2 mb-16 font-bold text-lg text-gray-800">
|
||||
{name} <span className="font-normal text-gray-500">#{uid}</span>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="info">
|
||||
<span className="label">{t("username")}</span>
|
||||
<span className="txt">
|
||||
{name} <span className="gray"> #{uid}</span>
|
||||
<div className="w-full flex justify-between mb-6">
|
||||
<div className="flex flex-col text-gray-500">
|
||||
<span className="text-xs uppercase font-semibold">{t("username")}</span>
|
||||
<span className="text-sm ">
|
||||
{name} <span className="text-gray-600"> #{uid}</span>
|
||||
</span>
|
||||
</div>
|
||||
<button data-edit="name" onClick={handleBasicEdit} className="btn">
|
||||
<Button data-edit="name" onClick={handleBasicEdit} className="">
|
||||
{ct("action.edit")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="info">
|
||||
<span className="label">{t("email")}</span>
|
||||
<span className="txt">{email}</span>
|
||||
<div className="w-full flex justify-between mb-6">
|
||||
<div className="flex flex-col text-gray-500">
|
||||
<span className="text-xs uppercase font-semibold">{t("email")}</span>
|
||||
<span className="text-sm">{email}</span>
|
||||
</div>
|
||||
<button data-edit="email" onClick={handleBasicEdit} className="btn">
|
||||
<Button data-edit="email" onClick={handleBasicEdit}>
|
||||
{ct("action.edit")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="info">
|
||||
<span className="label">{t("password")}</span>
|
||||
<span className="txt">*********</span>
|
||||
<div className="w-full flex justify-between mb-6">
|
||||
<div className="flex flex-col text-gray-500">
|
||||
<span className="text-xs uppercase font-semibold">{t("password")}</span>
|
||||
<span className="text-sm">*********</span>
|
||||
</div>
|
||||
<button onClick={togglePasswordModal} className="btn">
|
||||
<Button onClick={togglePasswordModal}>
|
||||
{ct("action.edit")}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{/* uid 1 是初始账户,不能删 */}
|
||||
{uid != 1 && (
|
||||
<div className="danger">
|
||||
<button className="btn" onClick={toggleRemoveAccountModalVisible}>
|
||||
<Button className="danger" onClick={toggleRemoveAccountModalVisible}>
|
||||
{t("delete_account")}
|
||||
</button>
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
{editModal && (
|
||||
<ProfileBasicEditModal
|
||||
type={editModal == "email" ? "email" : "text"}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ChangeEvent, useEffect, useState, FC } from "react";
|
||||
import styled from "styled-components";
|
||||
import toast from "react-hot-toast";
|
||||
import Input from "../../common/component/styled/Input";
|
||||
import { useUpdatePasswordMutation, useGetCredentialsQuery } from "../../app/services/auth";
|
||||
@@ -8,21 +7,6 @@ import Button from "../../common/component/styled/Button";
|
||||
import Modal from "../../common/component/Modal";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledEdit = styled(StyledModal)`
|
||||
.input {
|
||||
margin: 16px 0;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
label {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #6b7280;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
closeModal: () => void;
|
||||
@@ -75,9 +59,12 @@ const ProfileBasicEditModal: FC<Props> = ({ closeModal }) => {
|
||||
!confirmPassword ||
|
||||
newPassword !== confirmPassword ||
|
||||
isLoading;
|
||||
|
||||
const inputClass = `my-4 w-full flex flex-col gap-2`;
|
||||
const labelClass = `text-gray-400 font-semibold`;
|
||||
return (
|
||||
<Modal id="modal-modal">
|
||||
<StyledEdit
|
||||
<StyledModal
|
||||
title={t("change_pwd")}
|
||||
description={t("change_pwd_desc")}
|
||||
buttons={
|
||||
@@ -92,8 +79,8 @@ const ProfileBasicEditModal: FC<Props> = ({ closeModal }) => {
|
||||
}
|
||||
>
|
||||
{data?.password && (
|
||||
<div className="input">
|
||||
<label htmlFor={"current"}>{t("current_pwd")}</label>
|
||||
<div className={inputClass}>
|
||||
<label className={labelClass} htmlFor={"current"}>{t("current_pwd")}</label>
|
||||
<Input
|
||||
type="password"
|
||||
id="current"
|
||||
@@ -104,8 +91,8 @@ const ProfileBasicEditModal: FC<Props> = ({ closeModal }) => {
|
||||
></Input>
|
||||
</div>
|
||||
)}
|
||||
<div className="input">
|
||||
<label htmlFor={"newPassword"}>{t("new_pwd")}</label>
|
||||
<div className={inputClass}>
|
||||
<label className={labelClass} htmlFor={"newPassword"}>{t("new_pwd")}</label>
|
||||
<Input
|
||||
type="password"
|
||||
name={"newPassword"}
|
||||
@@ -114,8 +101,8 @@ const ProfileBasicEditModal: FC<Props> = ({ closeModal }) => {
|
||||
onChange={handleChange}
|
||||
></Input>
|
||||
</div>
|
||||
<div className="input">
|
||||
<label htmlFor={"confirmPassword"}>{t("confirm_new_pwd")}</label>
|
||||
<div className={inputClass}>
|
||||
<label className={labelClass} htmlFor={"confirmPassword"}>{t("confirm_new_pwd")}</label>
|
||||
<Input
|
||||
onBlur={handleCompare}
|
||||
type="password"
|
||||
@@ -125,7 +112,7 @@ const ProfileBasicEditModal: FC<Props> = ({ closeModal }) => {
|
||||
onChange={handleChange}
|
||||
></Input>
|
||||
</div>
|
||||
</StyledEdit>
|
||||
</StyledModal>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState, useEffect, ChangeEvent } from "react";
|
||||
import styled from "styled-components";
|
||||
import toast from "react-hot-toast";
|
||||
import {
|
||||
useChangeChannelTypeMutation,
|
||||
@@ -13,41 +12,11 @@ import Label from "../../common/component/styled/Label";
|
||||
import Radio from "../../common/component/styled/Radio";
|
||||
import Textarea from "../../common/component/styled/Textarea";
|
||||
import SaveTip from "../../common/component/SaveTip";
|
||||
import channelIcon from "../../assets/icons/channel.svg?url";
|
||||
import IconChannel from "../../assets/icons/channel.svg";
|
||||
import { useAppSelector } from "../../app/store";
|
||||
import { Channel } from "../../types/channel";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
position: relative;
|
||||
width: 512px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
.inputs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 24px;
|
||||
.input {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
.name {
|
||||
padding-left: 36px;
|
||||
background-image: url(${channelIcon});
|
||||
background-size: 20px;
|
||||
background-position-x: 8px;
|
||||
background-position-y: 8px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
`;
|
||||
export default function Overview({ id = 0 }) {
|
||||
const { t } = useTranslation("setting", { keyPrefix: "channel" });
|
||||
const { t: ct } = useTranslation();
|
||||
@@ -119,16 +88,16 @@ export default function Overview({ id = 0 }) {
|
||||
if (!values || !id) return null;
|
||||
const { name, description } = values;
|
||||
const readOnly = !loginUser?.is_admin && channel?.owner != loginUser?.uid;
|
||||
|
||||
const inputClass = `w-full flex flex-col items-start gap-2 relative`;
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="relative w-[512px] flex flex-col gap-6 h-full">
|
||||
<AvatarUploader type="channel" url={channel?.icon} name={name} uploadImage={updateIcon} />
|
||||
<div className="inputs">
|
||||
<div className="input">
|
||||
<div className="flex flex-col gap-6 items-start">
|
||||
<div className={inputClass}>
|
||||
<Label htmlFor="name">{t("name")}</Label>
|
||||
<Input
|
||||
disabled={readOnly}
|
||||
className="name"
|
||||
className="!pl-8"
|
||||
data-type="name"
|
||||
onChange={handleChange}
|
||||
value={name}
|
||||
@@ -136,8 +105,9 @@ export default function Overview({ id = 0 }) {
|
||||
id="name"
|
||||
placeholder={t("name")}
|
||||
/>
|
||||
<IconChannel className="absolute bottom-2.5 left-2" />
|
||||
</div>
|
||||
<div className="input">
|
||||
<div className={inputClass}>
|
||||
<Label htmlFor="desc">{t("topic")}</Label>
|
||||
<Textarea
|
||||
disabled={readOnly}
|
||||
@@ -149,7 +119,7 @@ export default function Overview({ id = 0 }) {
|
||||
id="name"
|
||||
placeholder={t("topic_placeholder")} />
|
||||
</div>
|
||||
{!readOnly && <div className="input">
|
||||
{!readOnly && <div className={inputClass}>
|
||||
<Label htmlFor="desc">{t("visibility")}</Label>
|
||||
<Radio
|
||||
options={[t("public"), t("private")]}
|
||||
@@ -164,6 +134,6 @@ export default function Overview({ id = 0 }) {
|
||||
</div>}
|
||||
</div>
|
||||
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={handleReset} />}
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import styled from "styled-components";
|
||||
import Tippy from "@tippyjs/react";
|
||||
import searchIcon from "../../assets/icons/search.svg?url";
|
||||
import addIcon from "../../assets/icons/add.svg?url";
|
||||
@@ -6,31 +5,6 @@ import AddEntriesMenu from "../../common/component/AddEntriesMenu";
|
||||
import Tooltip from "../../common/component/Tooltip";
|
||||
import { FC, ChangeEvent } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
position: relative;
|
||||
min-height: 56px;
|
||||
padding: 6px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.1);
|
||||
.search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
.input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
font-weight: normal;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
type Props = {
|
||||
input: string,
|
||||
updateInput: (input: string) => void
|
||||
@@ -41,17 +15,17 @@ const Search: FC<Props> = ({ input, updateInput }) => {
|
||||
updateInput(evt.target.value);
|
||||
};
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<div className="search">
|
||||
<div className="relative min-h-[56px] px-2 py-3 flex items-center justify-between gap-2 shadow">
|
||||
<div className="flex items-center gap-1">
|
||||
<img src={searchIcon} alt="search icon" />
|
||||
<input value={input} placeholder={`${t("action.search")}...`} className="input" onChange={handleInput} />
|
||||
<input value={input} placeholder={`${t("action.search")}...`} className="w-full text-sm" onChange={handleInput} />
|
||||
</div>
|
||||
<Tooltip tip={t("more")} placement="bottom">
|
||||
<Tippy interactive placement="bottom-end" trigger="click" content={<AddEntriesMenu />}>
|
||||
<img src={addIcon} alt="add icon" className="cursor-pointer" />
|
||||
</Tippy>
|
||||
</Tooltip>
|
||||
</StyledWrapper>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default Search;
|
||||
|
||||
Reference in New Issue
Block a user