feat: invite and reg

This commit is contained in:
zerosoul
2022-02-14 16:39:44 +08:00
parent 92d83e0dac
commit 6919b7404d
4 changed files with 338 additions and 86 deletions
+40 -33
View File
@@ -1,38 +1,45 @@
import { createApi } from "@reduxjs/toolkit/query/react";
import baseQuery from "./base.query";
import BASE_URL, { ContentTypes } from "../config";
import { REHYDRATE } from "redux-persist";
import { createApi } from '@reduxjs/toolkit/query/react';
import baseQuery from './base.query';
import BASE_URL, { ContentTypes } from '../config';
import { REHYDRATE } from 'redux-persist';
export const contactApi = createApi({
reducerPath: "contact",
baseQuery,
extractRehydrationInfo(action, { reducerPath }) {
if (action.type === REHYDRATE) {
return action.payload ? action.payload[reducerPath] : undefined;
}
},
endpoints: (builder) => ({
getContacts: builder.query({
query: () => ({ url: `user` }),
transformResponse: (data) => {
return data.map((user) => {
const avatar = `${BASE_URL}/resource/avatar?uid=${user.uid}`;
user.avatar = avatar;
return user;
});
},
}),
sendMsg: builder.mutation({
query: ({ id, content, type = "text" }) => ({
headers: {
"content-type": ContentTypes[type],
},
url: `user/${id}/send`,
method: "POST",
body: content,
}),
}),
reducerPath: 'contact',
baseQuery,
extractRehydrationInfo(action, { reducerPath }) {
if (action.type === REHYDRATE) {
return action.payload ? action.payload[reducerPath] : undefined;
}
},
endpoints: (builder) => ({
getContacts: builder.query({
query: () => ({ url: `user` }),
transformResponse: (data) => {
return data.map((user) => {
const avatar = `${BASE_URL}/resource/avatar?uid=${user.uid}`;
user.avatar = avatar;
return user;
});
}
}),
register: builder.mutation({
query: (data) => ({
url: `user/register`,
method: 'POST',
body: data
})
}),
sendMsg: builder.mutation({
query: ({ id, content, type = 'text' }) => ({
headers: {
'content-type': ContentTypes[type]
},
url: `user/${id}/send`,
method: 'POST',
body: content
})
})
})
});
export const { useGetContactsQuery, useSendMsgMutation } = contactApi;
export const { useGetContactsQuery, useSendMsgMutation, useRegisterMutation } = contactApi;
+55 -53
View File
@@ -1,70 +1,72 @@
// import { useState } from "react";
import { Route, Routes, HashRouter } from "react-router-dom";
import { Provider } from "react-redux";
import { Route, Routes, HashRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { persistStore } from "redux-persist";
import { PersistGate } from "redux-persist/integration/react";
import { persistStore } from 'redux-persist';
import { PersistGate } from 'redux-persist/integration/react';
// import Welcome from './Welcome'
import NotFoundPage from "./404";
import LoginPage from "./login";
import HomePage from "./home";
import ChatPage from "./chat";
import ContactsPage from "./contacts";
import RequireAuth from "../common/component/RequireAuth";
import NotFoundPage from './404';
import LoginPage from './login';
import HomePage from './home';
import ChatPage from './chat';
import ContactsPage from './contacts';
import RequireAuth from '../common/component/RequireAuth';
import store from "../app/store.with.persist";
import store from '../app/store.with.persist';
import InvitePage from './invite';
// import getStore from "../app/store";
const PageRoutes = () => {
return (
<HashRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/"
element={
<RequireAuth>
<HomePage />
</RequireAuth>
}
>
<Route index element={<ChatPage />} />
<Route path="chat">
<Route index element={<ChatPage />} />
<Route path="channel/:channel_id" element={<ChatPage />} />
<Route path="dm/:user_id" element={<ChatPage />} />
</Route>
<Route path="contacts">
<Route index element={<ContactsPage />} />
<Route path=":user_id" element={<ContactsPage />} />
</Route>
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</HashRouter>
);
return (
<HashRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/invite" element={<InvitePage />} />
<Route
path="/"
element={
<RequireAuth>
<HomePage />
</RequireAuth>
}
>
<Route index element={<ChatPage />} />
<Route path="chat">
<Route index element={<ChatPage />} />
<Route path="channel/:channel_id" element={<ChatPage />} />
<Route path="dm/:user_id" element={<ChatPage />} />
</Route>
<Route path="contacts">
<Route index element={<ContactsPage />} />
<Route path=":user_id" element={<ContactsPage />} />
</Route>
</Route>
<Route path="*" element={<NotFoundPage />} />
</Routes>
</HashRouter>
);
};
// const local_key = "AUTH_DATA";
export default function ReduxRoutes() {
// const [authData, setAuthData] = useState(
// JSON.parse(localStorage.getItem(local_key))
// );
// const updateAuthData = (data) => {
// localStorage.setItem(local_key, JSON.stringify(data));
// setAuthData(data);
// };
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistStore(store)}>
<PageRoutes />
</PersistGate>
{/* {authData ? (
// const [authData, setAuthData] = useState(
// JSON.parse(localStorage.getItem(local_key))
// );
// const updateAuthData = (data) => {
// localStorage.setItem(local_key, JSON.stringify(data));
// setAuthData(data);
// };
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistStore(store)}>
<PageRoutes />
</PersistGate>
{/* {authData ? (
<PersistGate loading={null} persistor={persistStore(getPersistStore())}>
<PageRoutes authData={authData} updateAuthData={updateAuthData} />
</PersistGate>
) : (
<PageRoutes authData={authData} updateAuthData={updateAuthData} />
)} */}
</Provider>
);
</Provider>
);
}
+168
View File
@@ -0,0 +1,168 @@
import { useState, useEffect } from 'react';
import StyledWrapper from './styled';
import { useNavigate } from 'react-router-dom';
import toast from 'react-hot-toast';
import { useRegisterMutation } from '../../app/services/contact';
import { useCheckInviteTokenValidMutation } from '../../app/services/auth';
export default function InvitePage() {
const [secondPwd, setSecondPwd] = useState('');
const [samePwd, setSamePwd] = useState(true);
const [token, setToken] = useState('');
const [valid, setValid] = useState(false);
// const [sp] = useSearchParams();
const navigateTo = useNavigate();
const [register, { data, isLoading, isSuccess, isError, error }] = useRegisterMutation();
const [checkToken, { data: isValid, isLoading: checkLoading, isSuccess: checkSuccess }] =
useCheckInviteTokenValidMutation();
useEffect(() => {
// console.log(search);
const query = new URLSearchParams(location.search);
setToken(query.get('token'));
}, []);
useEffect(() => {
if (token) {
checkToken(token);
}
}, [token]);
useEffect(() => {
if (checkSuccess) {
console.log({ isValid });
setValid(isValid);
} else {
setValid(false);
}
}, [checkSuccess, isValid]);
const [input, setInput] = useState({
name: '',
email: '',
password: ''
});
const handleReg = (evt) => {
evt.preventDefault();
if (!samePwd) {
toast.error('two passwords not same');
return;
}
console.log('wtf', input);
register({
...input,
magic_token: token,
gender: 1
});
};
const handleInput = (evt) => {
const { type } = evt.target.dataset;
const { value } = evt.target;
console.log(type, value);
setInput((prev) => {
prev[type] = value;
return { ...prev };
});
};
const handleSecondPwdInput = (evt) => {
const { value } = evt.target;
setSecondPwd(value);
};
const handlePwdCheck = () => {
if (secondPwd) {
setSamePwd(secondPwd == input.password);
}
};
useEffect(() => {
if (!samePwd) {
toast.error('two passwords not same');
}
}, [samePwd]);
useEffect(() => {
if (isSuccess && data) {
// 去登录
toast.success('register success, login please');
setTimeout(() => {
navigateTo('/login');
}, 500);
} else if (isError) {
console.log('register failed', error);
switch (error.status) {
case 412:
toast.error('register failed: invalid token or expired');
break;
case 409: {
const tips = {
email_conflict: 'email conflict',
name_conflict: 'name conflict'
};
toast.error(`register failed: ${tips[error.data?.reason]}`);
break;
}
default:
toast.error('register failed');
break;
}
}
}, [data, isSuccess, isError, error]);
const { email, password, name } = input;
if (!token) return 'token not found';
if (checkLoading) return 'checking token valid';
if (!valid) return 'invite token expires or invalid';
return (
<StyledWrapper>
<div className="form animate__animated animate__fadeInDown animate__faster">
<div className="tips">
<img
src="https://static.nicegoodthings.com/project/ext/webrowse.logo.png"
alt="logo"
className="logo"
/>
<h2 className="title">Sign Up to Rustchat</h2>
<span className="desc">Please enter your details.</span>
</div>
<form onSubmit={handleReg}>
<input
name="name"
value={name}
required
placeholder="Enter your name"
data-type="name"
onChange={handleInput}
/>
<input
name="email"
value={email}
required
placeholder="Enter your email"
data-type="email"
onChange={handleInput}
/>
<input
type="password"
value={password}
name="password"
required
data-type="password"
onChange={handleInput}
placeholder="Enter your password"
/>
<input
type="password"
value={secondPwd}
name="password"
required
data-type="password"
onBlur={handlePwdCheck}
onChange={handleSecondPwdInput}
placeholder="Enter your password again"
/>
<button disabled={isLoading || isSuccess} className="btn" type="submit">
Sign Up
</button>
</form>
</div>
</StyledWrapper>
);
}
+75
View File
@@ -0,0 +1,75 @@
import styled from 'styled-components';
const StyledWrapper = styled.div`
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
.form {
padding: 36px 40px 32px 40px;
/* border: 1px solid #eee; */
box-shadow: 0px 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;
padding-bottom: 24px;
.logo {
width: 56px;
height: 56px;
margin-bottom: 28px;
}
.title {
font-weight: 600;
font-size: 24px;
line-height: 32px;
color: #101828;
margin-bottom: 8px;
}
}
.desc {
font-weight: normal;
font-size: 16px;
line-height: 24px;
color: #667085;
}
form {
display: flex;
flex-direction: column;
gap: 20px;
input {
width: 360px;
background: #ffffff;
border: 1px solid #d0d5dd;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
padding: 10px 14px;
font-weight: normal;
font-size: 16px;
line-height: 24px;
color: #667085;
}
}
.btn {
display: inline-block;
text-align: center;
width: 100%;
font-weight: 500;
font-size: 16px;
line-height: 24px;
color: #ffffff;
padding: 10px;
background: #1fe1f9;
border: 1px solid #1fe1f9;
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
&.google {
color: #344054;
border-color: #d0d5dd;
background: none;
}
}
}
`;
export default StyledWrapper;