feat: lots of updates

This commit is contained in:
zerosoul
2022-01-27 22:06:44 +08:00
commit e18c7323a6
72 changed files with 12432 additions and 0 deletions
+186
View File
@@ -0,0 +1,186 @@
import { useEffect } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import Message from "../../common/component/Message";
import ChannelIcon from "../../common/component/ChannelIcon";
import Send from "../../common/component/Send";
import { useGetContactsQuery } from "../../app/services/contact";
// import msgs from "./channel.msg.mock.json";
import Contact from "../../common/component/Contact";
const StyledWrapper = styled.article`
position: relative;
width: 100%;
background: #fff;
height: 100vh;
overflow-y: scroll;
overflow-x: hidden;
.head {
height: 56px;
padding: 0 20px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
.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;
}
}
}
.main {
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
position: relative;
padding: 0 0 20px 16px;
.notification {
padding: 3px 8px;
font-style: normal;
font-weight: 600;
font-size: 12px;
line-height: 18px;
color: #fff;
position: absolute;
top: 0;
left: 10px;
width: 900px;
height: 24px;
background: linear-gradient(135deg, #3c8ce7 0%, #00eaff 100%);
border-radius: 0px 0px 8px 8px;
display: flex;
align-items: center;
justify-content: space-between;
.clear {
cursor: pointer;
color: inherit;
border: none;
background: none;
outline: none;
}
}
.channel {
width: 684px;
.info {
padding-top: 114px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 8px;
.title {
font-weight: bold;
font-size: 36px;
line-height: 44px;
}
.desc {
color: #78787c;
font-weight: 500;
font-size: 16px;
line-height: 24px;
}
.edit {
color: #3c8ce7;
padding: 0;
border: none;
outline: none;
background: none;
font-style: normal;
font-weight: 500;
font-size: 16px;
line-height: 24px;
}
}
.chat {
padding: 18px 0;
}
}
.contacts {
display: flex;
flex-direction: column;
gap: 5px;
/* todo */
width: 226px;
height: calc(100vh - 56px);
overflow-y: scroll;
background: #f5f6f7;
padding: 16px;
}
}
`;
export default function ChannelChat({ cid = "", data = {} }) {
const msgs = useSelector((store) => {
return store.channelMsg[cid] || {};
});
const { data: users } = useGetContactsQuery();
useEffect(() => {
console.log({ cid });
}, [cid]);
const { name, description, is_public, members = [] } = data;
const filteredUsers =
members.length == 0
? users
: users.filter((u) => {
return members.includes(u.uid);
});
console.log("channel message list", msgs);
return (
<StyledWrapper>
<header className="head">
<div className="txt">
<ChannelIcon personal={!is_public} />
<span className="title">{name}</span>
<span className="desc">{description}</span>
</div>
<ul className="members">members</ul>
</header>
<main className="main">
<div className="notification">
<div className="content">25+ new messages since 3:24 PM</div>
<button className="clear">Mark As Read</button>
</div>
<div className="channel">
<div className="info">
<h2 className="title">Welcome to #{name} !</h2>
<p className="desc">This is the start of the #{name} channel. </p>
<button className="edit">Edit Channel</button>
</div>
<div className="chat">
{Object.entries(msgs).map(([mid, msg]) => {
if (!msg) return null;
const { from_uid, content, created_at } = msg;
return (
<Message
key={mid}
time={created_at}
uid={from_uid}
content={content}
/>
);
})}
</div>
</div>
<div className="contacts">
{filteredUsers.map(({ name, status, uid }) => {
return <Contact key={name} uid={uid} status={status} />;
})}
</div>
</main>
<Send id={cid} type="channel" name={name} />
</StyledWrapper>
);
}
+94
View File
@@ -0,0 +1,94 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
import { useSelector } from "react-redux";
import Message from "../../common/component/Message";
import Send from "../../common/component/Send";
// import msgs from "./channel.msg.mock.json";
import Contact from "../../common/component/Contact";
import { useGetContactsQuery } from "../../app/services/contact";
const StyledWrapper = styled.article`
position: relative;
width: 100%;
background: #fff;
height: 100vh;
overflow-y: scroll;
overflow-x: hidden;
.head {
height: 56px;
padding: 0 20px 0 10px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
.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;
}
}
}
.main {
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
position: relative;
padding: 0 0 20px 16px;
.chat {
width: 890px;
padding: 18px 0;
}
}
`;
export default function DMChat({ uid = "" }) {
const msgs = useSelector((store) => {
return store.userMsg[uid] || {};
});
const { data: contacts } = useGetContactsQuery();
const [currUser, setCurrUser] = useState(null);
useEffect(() => {
console.log({ uid });
if (uid && contacts) {
setCurrUser(contacts.find((c) => c.uid == uid));
}
}, [uid, contacts]);
if (!currUser) return null;
console.log("user msgs", msgs);
return (
<StyledWrapper>
<header className="head">
<Contact interactive={false} uid={currUser.uid} />
{/* <ul className="members">members</ul> */}
</header>
<main className="main">
<div className="chat">
{Object.entries(msgs).map(([mid, msg]) => {
if (!msg) return null;
const { from_uid, content, created_at } = msg;
return (
<Message
key={mid}
time={created_at}
uid={from_uid}
content={content}
/>
);
})}
</div>
</main>
<Send type="user" name={currUser?.name} id={currUser?.uid} />
</StyledWrapper>
);
}
+152
View File
@@ -0,0 +1,152 @@
// import React from 'react';
import { useState } from "react";
import { NavLink, useParams } from "react-router-dom";
import { useSelector } from "react-redux";
import dayjs from "dayjs";
import { MdAdd } from "react-icons/md";
import { AiOutlineCaretDown } from "react-icons/ai";
import { useGetChannelsQuery } from "../../app/services/channel";
import { useGetContactsQuery } from "../../app/services/contact";
import StyledWrapper from "./styled";
import Search from "../../common/component/Search";
import Avatar from "../../common/component/Avatar";
import ChannelIcon from "../../common/component/ChannelIcon";
import ChannelChat from "./ChannelChat";
import DMChat from "./DMChat";
import ContactsModal from "../../common/component/ContactsModal";
import ChannelModal from "../../common/component/ChannelModal";
export default function ChatPage() {
const UserMsgData = useSelector((store) => {
return store.userMsg;
});
const [channelModalVisible, setChannelModalVisible] = useState(false);
const [contactsModalVisible, setContactsModalVisible] = useState(false);
const { channel_id, user_id } = useParams();
const { data: channels } = useGetChannelsQuery();
const { data: contacts } = useGetContactsQuery();
const toggleContactsModalVisible = () => {
setContactsModalVisible((prev) => !prev);
};
const toggleChannelModalVisible = () => {
setChannelModalVisible((prev) => !prev);
};
console.log("channels", channels);
if (!channels || !contacts) return null;
const Sessions = Object.keys(UserMsgData);
const tmpSessionUser = contacts.find((c) => c.uid == user_id);
return (
<>
{channelModalVisible && (
<ChannelModal closeModal={toggleChannelModalVisible} personal={true} />
)}
{contactsModalVisible && (
<ContactsModal closeModal={toggleContactsModalVisible} />
)}
<StyledWrapper>
<div className="left">
<Search />
<div className="list channels">
<h3 className="title">
<span className="txt">
<AiOutlineCaretDown size={18} color="#78787C" />
CHANNELS
</span>
<MdAdd
onClick={toggleChannelModalVisible}
size={18}
color="#78787C"
/>
</h3>
<nav className="nav">
{channels.map(({ gid, is_public, name, description }) => {
return (
<NavLink
title={description}
key={gid}
className="link"
to={`/chat/channel/${gid}`}
>
<span className="txt">
<ChannelIcon personal={!is_public} />
{name}
</span>
<i className="badge">12</i>
</NavLink>
);
})}
</nav>
</div>
<div className="list dms">
<h3 className="title">
<span className="txt">
<AiOutlineCaretDown size={18} color="#78787C" />
DIRECT MESSAGE
</span>
<MdAdd
size={18}
color="#78787C"
onClick={toggleContactsModalVisible}
/>
</h3>
<nav className="nav">
{Sessions.map((uid) => {
let currUser = contacts.find((c) => c.uid == uid);
let latestMid = Object.keys(UserMsgData[uid]).sort().pop();
let latestMsg = UserMsgData[uid][latestMid];
return (
<NavLink key={uid} className="session" to={`/chat/dm/${uid}`}>
<Avatar className="avatar" url={currUser.avatar} id={uid} />
<div className="details">
<div className="up">
<span className="name">{currUser.name}</span>
<time>
{dayjs(latestMsg.created_at).format("YYYY-MM-DD")}
</time>
</div>
<div className="down">
<div className="msg">{latestMsg.content}</div>
<i className="badge">3</i>
</div>
</div>
</NavLink>
);
})}
{user_id && !Sessions.includes(user_id) && (
<NavLink className="session" to={`/chat/dm/${user_id}`}>
<Avatar
className="avatar"
url={tmpSessionUser.avatar}
id={user_id}
/>
<div className="details">
<div className="up">
<span className="name">{tmpSessionUser.name}</span>
<time></time>
</div>
<div className="down">
<div className="msg"></div>
<i className="badge">3</i>
</div>
</div>
</NavLink>
)}
</nav>
</div>
</div>
<div className="right">
{channel_id && (
<ChannelChat
cid={channel_id}
data={channels.find(({ gid }) => gid == channel_id)}
/>
)}
{user_id && <DMChat uid={user_id} />}
</div>
</StyledWrapper>
</>
);
}
+153
View File
@@ -0,0 +1,153 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
display: flex;
height: 100%;
> .left {
display: flex;
flex-direction: column;
width: 260px;
box-shadow: inset -1px 0px 0px rgba(0, 0, 0, 0.1);
.list {
margin: 12px 8px;
.title {
padding: 0 8px;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
cursor: pointer;
> .txt {
user-select: none;
display: flex;
align-items: center;
gap: 5px;
font-weight: bold;
font-size: 12px;
line-height: 20px;
color: #78787c;
}
}
> .nav {
display: flex;
flex-direction: column;
gap: 4px;
a {
text-decoration: none;
}
.link {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px;
border-radius: 4px;
&:hover,
&.active {
background: rgba(116, 127, 141, 0.1);
}
> .txt {
display: flex;
align-items: center;
gap: 5px;
color: #1c1c1e;
font-weight: 600;
font-size: 14px;
line-height: 20px;
}
> .badge {
padding: 3px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 50%;
background: #bfbfbf;
font-weight: 900;
font-size: 10px;
line-height: 10px;
&.dot {
width: 6px;
height: 6px;
padding: 0;
}
}
}
.session {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 4px 8px;
border-radius: 4px;
&:hover,
&.active {
background: rgba(116, 127, 141, 0.1);
}
.avatar {
width: 32px;
height: 32px;
border-radius: 50%;
/* img{
width: 100%;
} */
}
.details {
display: flex;
flex-direction: column;
width: 100%;
.up {
display: flex;
justify-content: space-between;
.name {
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #52525b;
}
time {
font-weight: 500;
font-size: 12px;
line-height: 18px;
color: #78787c;
}
}
.down {
display: flex;
justify-content: space-between;
.msg {
font-weight: normal;
font-size: 12px;
line-height: 18px;
color: #78787c;
}
> .badge {
letter-spacing: -1px;
padding: 2px;
color: #fff;
height: 16px;
min-width: 16px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
background: #1fe1f9;
font-weight: 900;
font-size: 10px;
line-height: 10px;
&.mute {
background: #bfbfbf;
}
}
}
}
}
}
}
}
> .right {
width: 100%;
}
`;
export default StyledWrapper;