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
+6
View File
@@ -0,0 +1,6 @@
// import React from 'react';
import StyledWrapper from "./styled";
export default function NotFoundPage() {
return <StyledWrapper>404 page</StyledWrapper>;
}
+6
View File
@@ -0,0 +1,6 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
display: flex;
`;
export default StyledWrapper;
+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;
+92
View File
@@ -0,0 +1,92 @@
// import React from "react";
// import toast from "react-hot-toast";
import { useState, useEffect } from "react";
import { NavLink } from "react-router-dom";
import { BsChatText } from "react-icons/bs";
import { RiUserAddLine } from "react-icons/ri";
import { IoShareOutline } from "react-icons/io5";
import styled from "styled-components";
import { useGetContactsQuery } from "../../app/services/contact";
import Avatar from "../../common/component/Avatar";
const StyledWrapper = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-start;
margin-top: 80px;
width: 432px;
gap: 4px;
.avatar {
width: 80px;
height: 80px;
border-radius: 50%;
}
.name {
font-weight: bold;
font-size: 18px;
line-height: 100%;
color: #1c1c1e;
}
.email {
font-weight: normal;
font-size: 14px;
line-height: 20px;
color: #78787c;
}
.icons {
display: flex;
align-items: center;
padding: 26px 0;
gap: 28px;
.icon {
}
}
.line {
width: 100%;
height: 1px;
border: none;
background-color: rgba(0, 0, 0, 0.1);
}
`;
export default function Profile({ uid = null }) {
const { data: contacts } = useGetContactsQuery();
const [profile, setProfile] = useState(null);
useEffect(() => {
if (contacts && contacts) {
setProfile(contacts.find((c) => c.uid == uid));
} else {
setProfile(null);
}
}, [uid, contacts]);
if (!profile) return null;
console.log({ profile });
const { name, email, avatar } = profile;
return (
<StyledWrapper>
<Avatar className="avatar" url={avatar} id={uid} name={name} />
<h2 className="name">{name}</h2>
<span className="email">{email}</span>
<ul className="icons">
<li className="icon chat">
<NavLink to={`/chat/dm/${uid}`}>
<BsChatText size={20} color="#616161" />
</NavLink>
</li>
<li className="icon add">
{/* <NavLink to={`/chat/dm/${uid}`}> */}
<RiUserAddLine size={20} color="#616161" />
{/* </NavLink> */}
</li>
<li className="icon share">
{/* <NavLink to={`/chat/dm/${uid}`}> */}
<IoShareOutline size={20} color="#616161" />
{/* </NavLink> */}
</li>
</ul>
<hr className="line" />
</StyledWrapper>
);
}
+39
View File
@@ -0,0 +1,39 @@
// import { useState, useEffect } from "react";
import { NavLink, useParams } from "react-router-dom";
import { useGetContactsQuery } from "../../app/services/contact";
import Search from "../../common/component/Search";
import Contact from "../../common/component/Contact";
import Profile from "./Profile";
import StyledWrapper from "./styled";
export default function ContactsPage() {
const { user_id } = useParams();
const { data: contacts } = useGetContactsQuery();
console.log({ contacts, user_id });
if (!contacts) return null;
return (
<StyledWrapper>
<div className="left">
<Search />
<div className="list">
<nav className="nav">
{contacts.map(({ uid, status }) => {
return (
<NavLink key={uid} className="session" to={`/contacts/${uid}`}>
<Contact uid={uid} status={status} />
</NavLink>
);
})}
</nav>
</div>
</div>
{user_id && (
<div className="right">
<Profile uid={user_id} />
</div>
)}
</StyledWrapper>
);
}
+37
View File
@@ -0,0 +1,37 @@
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;
> .nav {
display: flex;
flex-direction: column;
gap: 4px;
a {
text-decoration: none;
}
.session {
&:hover,
&.active {
background: rgba(116, 127, 141, 0.1);
}
}
}
}
}
.right {
height: 100vh;
width: 100%;
display: flex;
justify-content: center;
align-items: flex-start;
}
`;
export default StyledWrapper;
+63
View File
@@ -0,0 +1,63 @@
// import React from 'react';
import styled from "styled-components";
import { AiOutlineCaretDown, AiOutlineSound } from "react-icons/ai";
import { MdOutlineKeyboardVoice } from "react-icons/md";
import { useSelector } from "react-redux";
const StyledWrapper = styled.div`
position: absolute;
bottom: 0;
left: 0;
padding: 16px 12px;
width: -webkit-fill-available;
display: flex;
align-items: center;
justify-content: space-between;
.profile {
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
gap: 5px;
.avatar {
width: 24px;
height: 24px;
border-radius: 50%;
}
.toggle {
}
}
.settings {
gap: 20px;
display: flex;
align-items: center;
justify-content: space-between;
.icon {
cursor: pointer;
}
}
`;
export default function CurrentUser({ collaspe = false }) {
const { user } = useSelector((store) => store.authData);
if (!user) return null;
const { name } = user;
return (
<StyledWrapper>
<div className="profile" title={name}>
<img
title={name}
src={`https://avatars.dicebear.com/api/adventurer-neutral/${name}.svg`}
alt="user avatar"
className="avatar"
/>
<AiOutlineCaretDown className="toggle" width={20} color="#C4C4C4" />
</div>
{!collaspe && (
<div className="settings">
<AiOutlineSound className="icon" size={15} color="#1C1C1E" />
<MdOutlineKeyboardVoice className="icon" size={15} color="#1C1C1E" />
</div>
)}
</StyledWrapper>
);
}
+71
View File
@@ -0,0 +1,71 @@
// import React from 'react';
import styled from "styled-components";
import { HiChevronDoubleLeft } from "react-icons/hi";
const StyledWrapper = styled.div`
height: 56px;
padding: 0 16px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.1);
&.collaspe {
padding-right: 5px;
}
.server {
display: flex;
align-items: center;
gap: 8px;
.logo {
width: 24px;
height: 24px;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px;
img {
width: 16px;
height: 16px;
}
}
.title {
white-space: nowrap;
font-weight: normal;
font-size: 14px;
line-height: 20px;
color: #4b5563;
}
}
.arrow {
cursor: pointer;
transform-origin: center;
transition: transform 0.5s ease-in-out;
display: flex;
width: 15px;
height: 15px;
.icon {
width: 100%;
height: 100%;
}
&.collaspe {
transform: rotate(180deg);
}
}
`;
export default function ServerDropList({ data, toggle, collaspe = false }) {
if (!data) return null;
return (
<StyledWrapper className={collaspe ? "collaspe" : ""}>
<div className="server">
<div className="logo">
<img src={data.logo} alt="logo" />
</div>
{!collaspe && <h2 className="title">{data.name}</h2>}
</div>
<div onClick={toggle} className={`arrow ${collaspe ? "collaspe" : ""}`}>
<HiChevronDoubleLeft className="icon" color="#BFBFBF" />
</div>
</StyledWrapper>
);
}
+93
View File
@@ -0,0 +1,93 @@
// import React from 'react';
import { RiAddFill } from "react-icons/ri";
import styled from "styled-components";
import { IoLogoGithub } from "react-icons/io5";
const StyledWrapper = styled.div`
padding: 0 6px;
display: flex;
align-items: center;
flex-direction: column;
align-items: flex-start;
gap: 20px;
> hr {
border: none;
width: 40%;
height: 1px;
background: rgba(255, 255, 255, 0.2);
}
.tools {
padding: 0 10px;
display: flex;
flex-direction: column;
gap: 10px;
.tool,
.add {
cursor: pointer;
gap: 9px;
display: flex;
align-items: center;
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #4b5563;
}
.tool {
.logo {
border-radius: 5.5px;
background: #fff;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
.icon {
width: 21px;
height: 21px;
}
}
.title {
white-space: nowrap;
}
&.add .logo {
background: none;
.icon {
width: 40px;
height: 40px;
}
}
}
}
`;
export default function Tools({ collaspe = false }) {
return (
<StyledWrapper>
<hr />
<ul className="tools">
<li className="tool">
<div className="logo">
<img
className="icon"
src="https://static.nicegoodthings.com/project/ext/webrowse.logo.png"
alt="logo"
/>
</div>
{!collaspe && <h2 className="title">Webrowse</h2>}
</li>
<li className="tool">
<div className="logo">
<IoLogoGithub size={40} className="icon" />
</div>
{!collaspe && <h2 className="title">Github</h2>}
</li>
<li className="tool add">
<div className="logo">
<RiAddFill className="icon" size={40} color="#4B5563" />
</div>
{!collaspe && <h2 className="title">Add new app</h2>}
</li>
</ul>
</StyledWrapper>
);
}
+50
View File
@@ -0,0 +1,50 @@
// import React from 'react';
import { useState } from "react";
import StyledWrapper from "./styled";
import { Outlet, NavLink } from "react-router-dom";
import ServerDropList from "./ServerDropList";
import Tools from "./Tools";
import usePreload from "./usePreload";
import CurrentUser from "./CurrentUser";
import ChatIcon from "../../assets/icons/chat.svg";
import ContactIcon from "../../assets/icons/contact.svg";
import NotificationHub from "../../common/component/NotificationHub";
export default function HomePage() {
const { data, error, success } = usePreload();
const [collaspe, setCollaspe] = useState(false);
const toggleCollaspe = () => {
setCollaspe((prev) => !prev);
};
console.log({ data, error, success });
return (
<>
<NotificationHub />
<StyledWrapper>
<div className={`col left ${collaspe ? "collaspe" : ""}`}>
<ServerDropList
data={data?.server}
collaspe={collaspe}
toggle={toggleCollaspe}
/>
<nav className="nav">
<NavLink className="link" to={"/chat"}>
<img src={ChatIcon} alt="chat icon" /> {!collaspe && `Chat`}
</NavLink>
<NavLink className="link" to={"/contacts"}>
<img src={ContactIcon} alt="contact icon" />{" "}
{!collaspe && `Contacts`}
</NavLink>
</nav>
<Tools collaspe={collaspe} />
<CurrentUser collaspe={collaspe} />
</div>
<div className="col right">
<Outlet />
</div>
</StyledWrapper>
</>
);
}
+49
View File
@@ -0,0 +1,49 @@
import styled from "styled-components";
const StyledWrapper = styled.div`
display: flex;
width: 100vw;
height: 100vh;
background-color: #f5f6f7;
> .col {
height: 100%;
display: flex;
flex-direction: column;
&.left {
position: relative;
/* background: #0891B2; */
width: 180px;
box-shadow: inset -1px 0px 0px rgba(0, 0, 0, 0.1);
transition: all 0.5s ease-in;
&.collaspe {
width: 64px;
}
}
&.right {
width: 100%;
}
> .nav {
display: flex;
flex-direction: column;
gap: 4px;
padding: 24px 8px 10px 8px;
.link {
display: flex;
align-items: center;
gap: 10px;
text-decoration: none;
padding: 10px 8px;
font-weight: 600;
font-size: 14px;
line-height: 20px;
color: #4b5563;
border-radius: 8px;
&:hover,
&.active {
background-color: rgba(0, 0, 0, 0.08);
}
}
}
}
`;
export default StyledWrapper;
+39
View File
@@ -0,0 +1,39 @@
// import React from 'react';
import { useGetContactsQuery } from "../../app/services/contact";
import { useGetChannelsQuery } from "../../app/services/channel";
import { useGetServerQuery } from "../../app/services/server";
// pollingInterval: 0,
const querySetting = {
refetchOnMountOrArgChange: true,
};
export default function usePreload() {
const {
isLoading: contactsLoading,
isSuccess: contactsSuccess,
isError: contactsError,
data: contacts,
} = useGetContactsQuery(undefined, querySetting);
const {
isLoading: serverLoading,
isSuccess: serverSuccess,
isError: serverError,
data: server,
} = useGetServerQuery(undefined, querySetting);
const {
isLoading: groupsLoading,
isSuccess: groupsSuccess,
isError: groupsError,
data: groups,
} = useGetChannelsQuery(undefined, querySetting);
return {
loading: contactsLoading && groupsLoading && serverLoading,
error: contactsError && groupsError && serverError,
success: contactsSuccess && groupsSuccess && serverSuccess,
data: {
contacts,
server,
groups,
},
};
}
+81
View File
@@ -0,0 +1,81 @@
import { useState } from "react";
import StyledWrapper from "./styled";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import toast from "react-hot-toast";
import { useLoginMutation } from "../../app/services/auth";
import { setAuthData } from "../../app/slices/auth.data";
export default function LoginPage() {
const navigateTo = useNavigate();
const dispatch = useDispatch();
const [login] = useLoginMutation();
const [input, setInput] = useState({
email: "",
password: "",
});
const handleLogin = async (evt) => {
evt.preventDefault();
console.log("wtf", input);
const { data, error } = await login({
...input,
device: "web",
device_token: "test",
});
if (error) {
console.log(error);
switch (error.status) {
case 401:
toast.error("username or password incorrect");
break;
default:
break;
}
return;
}
if (data) {
dispatch(setAuthData(data));
toast.success("login success");
navigateTo("/");
}
};
const handleInput = (evt) => {
const { type } = evt.target.dataset;
const { value } = evt.target;
console.log(type, value);
setInput((prev) => {
prev[type] = value;
return { ...prev };
});
};
const { email, password } = input;
return (
<StyledWrapper>
<div className="form">
<form onSubmit={handleLogin}>
<input
name="email"
value={email}
required
placeholder="email"
data-type="email"
onChange={handleInput}
/>
<input
type="password"
value={password}
name="password"
required
data-type="password"
onChange={handleInput}
placeholder="password"
/>
<button type="submit">login</button>
</form>
</div>
</StyledWrapper>
);
}
+22
View File
@@ -0,0 +1,22 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
.form{
padding: 30px 15px;
border: 1px solid #eee;
form{
display: flex;
flex-direction: column;
gap: 5px;
input{
padding:4px 6px
}
}
}
`;
export default StyledWrapper;