feat: invite and reg
This commit is contained in:
+40
-33
@@ -1,38 +1,45 @@
|
|||||||
import { createApi } from "@reduxjs/toolkit/query/react";
|
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||||
import baseQuery from "./base.query";
|
import baseQuery from './base.query';
|
||||||
import BASE_URL, { ContentTypes } from "../config";
|
import BASE_URL, { ContentTypes } from '../config';
|
||||||
import { REHYDRATE } from "redux-persist";
|
import { REHYDRATE } from 'redux-persist';
|
||||||
|
|
||||||
export const contactApi = createApi({
|
export const contactApi = createApi({
|
||||||
reducerPath: "contact",
|
reducerPath: 'contact',
|
||||||
baseQuery,
|
baseQuery,
|
||||||
extractRehydrationInfo(action, { reducerPath }) {
|
extractRehydrationInfo(action, { reducerPath }) {
|
||||||
if (action.type === REHYDRATE) {
|
if (action.type === REHYDRATE) {
|
||||||
return action.payload ? action.payload[reducerPath] : undefined;
|
return action.payload ? action.payload[reducerPath] : undefined;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
getContacts: builder.query({
|
getContacts: builder.query({
|
||||||
query: () => ({ url: `user` }),
|
query: () => ({ url: `user` }),
|
||||||
transformResponse: (data) => {
|
transformResponse: (data) => {
|
||||||
return data.map((user) => {
|
return data.map((user) => {
|
||||||
const avatar = `${BASE_URL}/resource/avatar?uid=${user.uid}`;
|
const avatar = `${BASE_URL}/resource/avatar?uid=${user.uid}`;
|
||||||
user.avatar = avatar;
|
user.avatar = avatar;
|
||||||
return user;
|
return user;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
}),
|
|
||||||
sendMsg: builder.mutation({
|
|
||||||
query: ({ id, content, type = "text" }) => ({
|
|
||||||
headers: {
|
|
||||||
"content-type": ContentTypes[type],
|
|
||||||
},
|
|
||||||
url: `user/${id}/send`,
|
|
||||||
method: "POST",
|
|
||||||
body: content,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
|
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 { useState } from "react";
|
||||||
import { Route, Routes, HashRouter } from "react-router-dom";
|
import { Route, Routes, HashRouter } from 'react-router-dom';
|
||||||
import { Provider } from "react-redux";
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { persistStore } from "redux-persist";
|
import { persistStore } from 'redux-persist';
|
||||||
import { PersistGate } from "redux-persist/integration/react";
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
// import Welcome from './Welcome'
|
// import Welcome from './Welcome'
|
||||||
import NotFoundPage from "./404";
|
import NotFoundPage from './404';
|
||||||
import LoginPage from "./login";
|
import LoginPage from './login';
|
||||||
import HomePage from "./home";
|
import HomePage from './home';
|
||||||
import ChatPage from "./chat";
|
import ChatPage from './chat';
|
||||||
import ContactsPage from "./contacts";
|
import ContactsPage from './contacts';
|
||||||
import RequireAuth from "../common/component/RequireAuth";
|
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";
|
// import getStore from "../app/store";
|
||||||
|
|
||||||
const PageRoutes = () => {
|
const PageRoutes = () => {
|
||||||
return (
|
return (
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route
|
<Route path="/invite" element={<InvitePage />} />
|
||||||
path="/"
|
<Route
|
||||||
element={
|
path="/"
|
||||||
<RequireAuth>
|
element={
|
||||||
<HomePage />
|
<RequireAuth>
|
||||||
</RequireAuth>
|
<HomePage />
|
||||||
}
|
</RequireAuth>
|
||||||
>
|
}
|
||||||
<Route index element={<ChatPage />} />
|
>
|
||||||
<Route path="chat">
|
<Route index element={<ChatPage />} />
|
||||||
<Route index element={<ChatPage />} />
|
<Route path="chat">
|
||||||
<Route path="channel/:channel_id" element={<ChatPage />} />
|
<Route index element={<ChatPage />} />
|
||||||
<Route path="dm/:user_id" element={<ChatPage />} />
|
<Route path="channel/:channel_id" element={<ChatPage />} />
|
||||||
</Route>
|
<Route path="dm/:user_id" element={<ChatPage />} />
|
||||||
<Route path="contacts">
|
</Route>
|
||||||
<Route index element={<ContactsPage />} />
|
<Route path="contacts">
|
||||||
<Route path=":user_id" element={<ContactsPage />} />
|
<Route index element={<ContactsPage />} />
|
||||||
</Route>
|
<Route path=":user_id" element={<ContactsPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="*" element={<NotFoundPage />} />
|
</Route>
|
||||||
</Routes>
|
<Route path="*" element={<NotFoundPage />} />
|
||||||
</HashRouter>
|
</Routes>
|
||||||
);
|
</HashRouter>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
// const local_key = "AUTH_DATA";
|
// const local_key = "AUTH_DATA";
|
||||||
export default function ReduxRoutes() {
|
export default function ReduxRoutes() {
|
||||||
// const [authData, setAuthData] = useState(
|
// const [authData, setAuthData] = useState(
|
||||||
// JSON.parse(localStorage.getItem(local_key))
|
// JSON.parse(localStorage.getItem(local_key))
|
||||||
// );
|
// );
|
||||||
// const updateAuthData = (data) => {
|
// const updateAuthData = (data) => {
|
||||||
// localStorage.setItem(local_key, JSON.stringify(data));
|
// localStorage.setItem(local_key, JSON.stringify(data));
|
||||||
// setAuthData(data);
|
// setAuthData(data);
|
||||||
// };
|
// };
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PersistGate loading={null} persistor={persistStore(store)}>
|
<PersistGate loading={null} persistor={persistStore(store)}>
|
||||||
<PageRoutes />
|
<PageRoutes />
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
{/* {authData ? (
|
{/* {authData ? (
|
||||||
<PersistGate loading={null} persistor={persistStore(getPersistStore())}>
|
<PersistGate loading={null} persistor={persistStore(getPersistStore())}>
|
||||||
<PageRoutes authData={authData} updateAuthData={updateAuthData} />
|
<PageRoutes authData={authData} updateAuthData={updateAuthData} />
|
||||||
</PersistGate>
|
</PersistGate>
|
||||||
) : (
|
) : (
|
||||||
<PageRoutes authData={authData} updateAuthData={updateAuthData} />
|
<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