feat: lots updates

This commit is contained in:
zerosoul
2022-01-30 21:30:12 +08:00
parent e18c7323a6
commit ceb02d834f
38 changed files with 1943 additions and 1832 deletions
@@ -7,7 +7,6 @@ import ChannelIcon from "../ChannelIcon";
import Contact from "../Contact";
import StyledWrapper from "./styled";
import useFilteredUsers from "../../hook/useFilteredUsers";
import { useGetChannelsQuery } from "../../../app/services/channel";
import { useCreateChannelMutation } from "../../../app/services/channel";
export default function ChannelModal({ personal = false, closeModal }) {
@@ -19,7 +18,6 @@ export default function ChannelModal({ personal = false, closeModal }) {
is_public: !personal,
});
const { contacts, input, updateInput } = useFilteredUsers();
const { refetch: refetchChannels } = useGetChannelsQuery();
const [
createChannel,
{ isSuccess, isError, isLoading, data: newChannel },
@@ -49,7 +47,6 @@ export default function ChannelModal({ personal = false, closeModal }) {
if (isSuccess) {
toast.success("create new channel success");
closeModal();
refetchChannels();
navigateTo(`/chat/channel/${newChannel.gid}`);
}
}, [isSuccess, newChannel]);
+36 -8
View File
@@ -1,10 +1,12 @@
// import React from 'react';
import { useEffect } from "react";
import styled from "styled-components";
import dayjs from "dayjs";
import { useDispatch } from "react-redux";
import { useInViewRef } from "rooks";
import Avatar from "./Avatar";
import { useGetContactsQuery } from "../../app/services/contact";
import { setChannelMsgRead } from "../../app/slices/message.channel";
import { setUserMsgRead } from "../../app/slices/message.user";
const StyledMsg = styled.div`
display: flex;
align-items: flex-start;
@@ -44,24 +46,50 @@ const StyledMsg = styled.div`
font-size: 14px;
line-height: 20px;
word-break: break-all;
white-space: break-spaces;
&.pending {
opacity: 0.5;
}
}
}
`;
export default function Message({ uid, time, content }) {
export default function Message({
gid = "",
mid = "",
uid,
fromUid,
time,
content,
unread = false,
pending,
}) {
const [myRef, inView] = useInViewRef();
const disptach = useDispatch();
// const wrapperRef = useRef(null);
const { data: contacts } = useGetContactsQuery();
useEffect(() => {
// if (wrapperRef) {
// wrapperRef.current.scrollIntoView();
if (inView && unread) {
const setMsgRead = gid ? setChannelMsgRead : setUserMsgRead;
disptach(setMsgRead({ id: gid || uid, mid }));
}
// }
}, [gid, mid, uid, unread, inView]);
if (!contacts) return null;
const currUser = contacts.find((c) => c.uid == uid);
const currUser = contacts.find((c) => c.uid == fromUid) || {};
return (
<StyledMsg>
<StyledMsg ref={myRef}>
<div className="avatar" data-uid={uid}>
<Avatar url={currUser.avatar} id={uid} name={currUser.name} />
<Avatar url={currUser.avatar} id={fromUid} name={currUser.name} />
</div>
<div className="details">
<div className="up">
<span className="name">{currUser.name}</span>
<i className="time">{dayjs(time).format("YYYY-MM-DD h:mm:ss A")}</i>
</div>
<div className="down">{content}</div>
<div className={`down ${pending ? "pending" : ""}`}>{content}</div>
</div>
</StyledMsg>
);
+53 -8
View File
@@ -1,14 +1,23 @@
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import toast from "react-hot-toast";
import { useDispatch } from "react-redux";
import BASE_URL from "../../app/config";
import {
setChannels,
addChannel,
deleteChannel,
} from "../../app/slices/channels";
import { clearAuthData } from "../../app/slices/auth.data";
import { addChannelMsg } from "../../app/slices/message.channel";
import { addUserMsg } from "../../app/slices/message.user";
const NotificationHub = () => {
const NotificationHub = ({ token }) => {
const dispatch = useDispatch();
const { token } = useSelector((store) => store.authData);
const navigate = useNavigate();
useEffect(() => {
let sse = null;
if (token) {
@@ -24,6 +33,7 @@ const NotificationHub = () => {
};
}
return () => {
console.log("re-run see init");
if (sse) {
sse.close();
}
@@ -31,12 +41,44 @@ const NotificationHub = () => {
}, [token]);
const handleSSEMessage = (data) => {
const { type } = data;
switch (type) {
case "heartbeat":
console.log("heartbeat");
break;
case "kick":
{
console.log("kicked");
switch (data.reason) {
case "login_from_other_device":
dispatch(clearAuthData());
navigate("/login");
toast("kicked from the other device");
break;
case "delete_user":
dispatch(clearAuthData());
navigate("/login");
toast("sorry, your account has been deleted");
break;
default:
break;
}
}
break;
case "related_groups":
console.log("joined group list", data);
dispatch(setChannels(data.groups));
break;
case "joined_group":
console.log("joined group list", data.group);
dispatch(addChannel(data.group));
break;
case "kick_from_group":
console.log("joined group list", data.gid);
dispatch(deleteChannel(data.gid));
break;
case "chat":
console.log("chat data", data);
// console.log("chat data", data);
if (data.gid) {
const { gid, ...rest } = data;
dispatch(addChannelMsg({ id: gid, ...rest }));
@@ -46,10 +88,13 @@ const NotificationHub = () => {
break;
default:
console.log("sse event data", data);
break;
}
};
return null;
};
export default NotificationHub;
function compareToken(prevHub, nextHub) {
return prevHub.token === nextHub.token;
}
export default React.memo(NotificationHub, compareToken);
+9
View File
@@ -0,0 +1,9 @@
// import React from 'react'
import { Navigate } from "react-router-dom";
import { useSelector } from "react-redux";
export default function RequireAuth({ children, redirectTo = "/login" }) {
const { token } = useSelector((store) => store.authData);
return token ? children : <Navigate to={redirectTo} />;
}
+1
View File
@@ -21,6 +21,7 @@ const StyledWrapper = styled.div`
align-items: center;
gap: 5px;
.input {
width: 100%;
border: none;
outline: none;
background: none;
-131
View File
@@ -1,131 +0,0 @@
import { useState, useEffect } from "react";
import styled from "styled-components";
import { MdAdd } from "react-icons/md";
import TextareaAutosize from "react-textarea-autosize";
import { useDispatch } from "react-redux";
import { useSendChannelMsgMutation } from "../../app/services/channel";
import { useSendMsgMutation } from "../../app/services/contact";
import { addChannelMsg } from "../../app/slices/message.channel";
import { addUserMsg } from "../../app/slices/message.user";
const StyledSend = styled.div`
position: sticky;
top: calc(100vh - 90px);
left: 16px;
background: #f5f6f7;
border-radius: 8px;
width: 884px;
min-height: 54px;
display: flex;
align-items: center;
gap: 18px;
padding: 4px 18px;
.addon {
cursor: pointer;
}
.input {
width: 100%;
position: relative;
.content {
padding: 4px;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #616161;
width: 100%;
border: none;
background: none;
}
.btn {
cursor: pointer;
border: none;
border-radius: 4px;
padding: 2px 6px;
background: green;
color: #fff;
font-size: 16px;
font-weight: bold;
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
}
}
`;
const Types = {
channel: "#",
user: "@",
};
export default function Send({ name, type = "channel", id = "" }) {
const dispatch = useDispatch();
const [
sendMsg,
{ isLoading: sending, isSuccess: sendSuccess, data: sendData },
] = useSendMsgMutation();
const [
sendChannelMsg,
{
isLoading: channelSending,
isSuccess: sendChannelSuccess,
data: sendChannelData,
},
] = useSendChannelMsgMutation();
const [msg, setMsg] = useState("");
const handleMsgChange = (evt) => {
setMsg(evt.target.value);
};
useEffect(() => {
if (sendSuccess) {
dispatch(addUserMsg({ id, ...sendData }));
setMsg("");
}
}, [sendSuccess, sendData]);
useEffect(() => {
if (sendChannelSuccess) {
const { gid, ...rest } = sendChannelData;
dispatch(addChannelMsg({ id: gid, ...rest }));
setMsg("");
}
}, [sendChannelSuccess, sendChannelData]);
const handleSendMessage = () => {
if (!msg || !type || !id) return;
switch (type) {
case "channel":
sendChannelMsg({ gid: id, message: msg });
break;
case "user":
sendMsg({ uid: id, message: msg });
break;
default:
break;
}
};
return (
<StyledSend className="send">
<MdAdd className="addon" size={20} color="#78787C" />
<div className="input">
<TextareaAutosize
className="content"
maxRows={8}
minRows={1}
onChange={handleMsgChange}
value={msg}
placeholder={`${Types[type]}${name} 发消息`}
/>
<button
disabled={sending || channelSending}
className="btn"
onClick={handleSendMessage}
>
{/* {sending ? `Sending` : `Send`} */}
Send
</button>
</div>
<div className="emoji">emoji</div>
</StyledSend>
);
}
+129
View File
@@ -0,0 +1,129 @@
import { useState, useEffect, useRef } from "react";
import { MdAdd } from "react-icons/md";
import TextareaAutosize from "react-textarea-autosize";
import { useDispatch } from "react-redux";
import { useKey } from "rooks";
import "emoji-mart/css/emoji-mart.css";
import { Picker } from "emoji-mart";
import { useSendChannelMsgMutation } from "../../../app/services/channel";
import { useSendMsgMutation } from "../../../app/services/contact";
import { addChannelMsg } from "../../../app/slices/message.channel";
import { addUserMsg } from "../../../app/slices/message.user";
import StyledSend from "./styled";
const Types = {
channel: "#",
user: "@",
};
export default function Send({ name, type = "channel", id = "" }) {
const inputRef = useRef();
const [emojiPicker, setEmojiPicker] = useState(false);
const [shift, setShift] = useState(false);
const [enter, setEnter] = useState(false);
const [msg, setMsg] = useState("");
const dispatch = useDispatch();
const toggleEmojiPicker = () => {
setEmojiPicker((prev) => !prev);
};
const [
sendMsg,
{ isLoading: sending, isSuccess: sendSuccess, data: sendData },
] = useSendMsgMutation();
const [
sendChannelMsg,
{
isLoading: channelSending,
isSuccess: sendChannelSuccess,
data: sendChannelData,
},
] = useSendChannelMsgMutation();
useKey(
"Shift",
(e) => {
console.log("shift", e.type);
setShift(e.type == "keydown");
},
{ eventTypes: ["keydown", "keyup"], target: inputRef }
);
const handleMsgChange = (evt) => {
if (enter && !shift) {
handleSendMessage();
} else {
setMsg(evt.target.value);
// inputRef.current.focus();
}
};
const handleInputKeydown = (e) => {
setEnter(e.key === "Enter");
};
const handleEmojiSelect = (emoji) => {
console.log(emoji);
setMsg((prev) => `${prev}${emoji.native}`);
// inputRef.current.focus();
toggleEmojiPicker();
};
useEffect(() => {
if (sendSuccess) {
dispatch(addUserMsg({ id, ...sendData, unread: false }));
setMsg("");
}
}, [sendSuccess, sendData]);
useEffect(() => {
if (sendChannelSuccess) {
const { gid, ...rest } = sendChannelData;
dispatch(addChannelMsg({ id: gid, ...rest, unread: false }));
setMsg("");
}
}, [sendChannelSuccess, sendChannelData]);
useEffect(() => {
inputRef.current.focus();
}, [msg]);
const handleSendMessage = () => {
if (!msg || !type || !id) return;
switch (type) {
case "channel":
sendChannelMsg({ gid: id, message: msg });
break;
case "user":
sendMsg({ uid: id, message: msg });
break;
default:
break;
}
};
return (
<StyledSend className="send">
<MdAdd className="addon" size={20} color="#78787C" />
<div className="input">
<TextareaAutosize
// autoFocus
ref={inputRef}
className="content"
maxRows={8}
minRows={1}
onKeyDown={handleInputKeydown}
onChange={handleMsgChange}
value={msg}
placeholder={`${Types[type]}${name} 发消息`}
/>
</div>
<div className="emoji">
<button className="toggle" onClick={toggleEmojiPicker}>
😄
</button>
{emojiPicker && (
<div className="picker">
<Picker
onSelect={handleEmojiSelect}
showPreview={false}
showSkinTones={false}
/>
</div>
)}
</div>
</StyledSend>
);
}
+65
View File
@@ -0,0 +1,65 @@
import styled from "styled-components";
const StyledSend = styled.div`
position: absolute;
bottom: 15px;
left: 50%;
transform: translateX(-50%);
background: #f5f6f7;
border-radius: 8px;
width: calc(100% - 32px);
min-height: 54px;
display: flex;
align-items: center;
gap: 18px;
padding: 4px 18px;
/* margin: 0 16px; */
.addon {
cursor: pointer;
}
.input {
width: 100%;
position: relative;
.content {
outline: none;
padding: 4px;
font-weight: 500;
font-size: 14px;
line-height: 20px;
color: #616161;
width: 100%;
border: none;
background: none;
}
.btn {
cursor: pointer;
border: none;
border-radius: 4px;
padding: 2px 6px;
background: green;
color: #fff;
font-size: 16px;
font-weight: bold;
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
}
}
.emoji {
position: relative;
.toggle {
font-size: 22px;
border: none;
background: none;
}
.picker {
position: absolute;
top: -15px;
right: 0;
transform: translateY(-100%);
}
}
`;
export default StyledSend;