feat: invite and reg
This commit is contained in:
+40
-33
@@ -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
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
Reference in New Issue
Block a user