feat: login config

This commit is contained in:
zerosoul
2022-03-31 17:43:58 +08:00
parent b2254c5fb7
commit 265d4653a7
7 changed files with 686 additions and 570 deletions
+87 -93
View File
@@ -1,106 +1,100 @@
import { fetchBaseQuery } from "@reduxjs/toolkit/query"; import { fetchBaseQuery } from '@reduxjs/toolkit/query';
import toast from "react-hot-toast"; import toast from 'react-hot-toast';
import dayjs from "dayjs"; import dayjs from 'dayjs';
import { updateToken, resetAuthData } from "../slices/auth.data"; import { updateToken, resetAuthData } from '../slices/auth.data';
import BASE_URL, { tokenHeader } from "../config"; import BASE_URL, { tokenHeader } from '../config';
const whiteList = [ const whiteList = [
"login", 'login',
"register", 'register',
"checkInviteTokenValid", 'checkInviteTokenValid',
"getServer", 'getLoginConfig',
"getOpenid", 'getServer',
"getMetamaskNonce", 'getOpenid',
"renew", 'getMetamaskNonce',
'renew'
]; ];
const baseQuery = fetchBaseQuery({ const baseQuery = fetchBaseQuery({
baseUrl: BASE_URL, baseUrl: BASE_URL,
prepareHeaders: (headers, { getState, endpoint }) => { prepareHeaders: (headers, { getState, endpoint }) => {
console.log("req", endpoint); console.log('req', endpoint);
const { token } = getState().authData; const { token } = getState().authData;
if (token && !whiteList.includes(endpoint)) { if (token && !whiteList.includes(endpoint)) {
headers.set(tokenHeader, token); headers.set(tokenHeader, token);
} }
return headers; return headers;
}, }
}); });
let waitingForRenew = null; let waitingForRenew = null;
const baseQueryWithTokenCheck = async (args, api, extraOptions) => { const baseQueryWithTokenCheck = async (args, api, extraOptions) => {
if (waitingForRenew) { if (waitingForRenew) {
await waitingForRenew; await waitingForRenew;
} }
// 先检查token是否过期,过期则renew // 先检查token是否过期,过期则renew
const { const { token, refreshToken, expireTime = new Date().getTime() } = api.getState().authData;
token, let result = null;
refreshToken, if (!whiteList.includes(api.endpoint) && dayjs().isAfter(new Date(expireTime - 20 * 1000))) {
expireTime = new Date().getTime(), // 快过期了,renew
} = api.getState().authData; waitingForRenew = baseQuery(
let result = null; {
if ( url: '/token/renew',
!whiteList.includes(api.endpoint) && method: 'POST',
dayjs().isAfter(new Date(expireTime - 20 * 1000)) body: {
) { token,
// 快过期了,renew refresh_token: refreshToken
waitingForRenew = baseQuery(
{
url: "/token/renew",
method: "POST",
body: {
token,
refresh_token: refreshToken,
},
},
api,
extraOptions
);
result = await waitingForRenew;
waitingForRenew = null;
if (result.data) {
// store the new token
api.dispatch(updateToken(result.data));
// retry the initial query
result = await baseQuery(args, api, extraOptions);
} }
} else { },
result = await baseQuery(args, api, extraOptions); api,
extraOptions
);
result = await waitingForRenew;
waitingForRenew = null;
if (result.data) {
// store the new token
api.dispatch(updateToken(result.data));
// retry the initial query
result = await baseQuery(args, api, extraOptions);
} }
if (result?.error) { } else {
console.log("api error", result.error, api.endpoint); result = await baseQuery(args, api, extraOptions);
switch (result.error.originalStatus || result.error.status) { }
case "FETCH_ERROR": if (result?.error) {
{ console.log('api error', result.error, api.endpoint);
toast.error(`${api.endpoint}: Failed to fetch`); switch (result.error.originalStatus || result.error.status) {
} case 'FETCH_ERROR':
break; {
case 404: toast.error(`${api.endpoint}: Failed to fetch`);
{
toast.error("Request Not Found");
}
break;
case 500:
{
toast.error(result.error.data || "server error");
}
break;
case 401:
{
// if (api.endpoint === "renew") {
// toast.error("token expired, please login again");
api.dispatch(resetAuthData());
location.href = "/#/login";
// } else {
toast.error("API Not Authenticated");
// return;
// }
}
break;
case 403:
toast.error("Request Not Allowed");
break;
default:
break;
} }
break;
case 404:
{
toast.error('Request Not Found');
}
break;
case 500:
{
toast.error(result.error.data || 'server error');
}
break;
case 401:
{
// if (api.endpoint === "renew") {
// toast.error("token expired, please login again");
api.dispatch(resetAuthData());
location.href = '/#/login';
// } else {
toast.error('API Not Authenticated');
// return;
// }
}
break;
case 403:
toast.error('Request Not Allowed');
break;
default:
break;
} }
return result; }
return result;
}; };
export default baseQueryWithTokenCheck; export default baseQueryWithTokenCheck;
+149 -140
View File
@@ -1,147 +1,156 @@
import { createApi } from "@reduxjs/toolkit/query/react"; import { createApi } from '@reduxjs/toolkit/query/react';
import BASE_URL from "../config"; import BASE_URL from '../config';
import { updateInviteLink, updateInfo } from "../slices/server"; import { updateInviteLink, updateInfo } from '../slices/server';
import baseQuery from "./base.query"; import baseQuery from './base.query';
const defaultExpireDuration = 7 * 24 * 60 * 60; const defaultExpireDuration = 7 * 24 * 60 * 60;
export const serverApi = createApi({ export const serverApi = createApi({
reducerPath: "serverApi", reducerPath: 'serverApi',
baseQuery, baseQuery,
endpoints: (builder) => ({ endpoints: (builder) => ({
getServer: builder.query({ getServer: builder.query({
query: () => ({ url: `admin/system/organization` }), query: () => ({ url: `admin/system/organization` }),
transformResponse: (data) => { transformResponse: (data) => {
data.logo = `${BASE_URL}/resource/organization/logo?t=${new Date().getTime()}`; data.logo = `${BASE_URL}/resource/organization/logo?t=${new Date().getTime()}`;
return data; return data;
}, },
async onQueryStarted(data, { dispatch, queryFulfilled }) { async onQueryStarted(data, { dispatch, queryFulfilled }) {
try { try {
const { data: server } = await queryFulfilled; const { data: server } = await queryFulfilled;
dispatch(updateInfo(server)); dispatch(updateInfo(server));
} catch { } catch {
console.log("get server info error"); console.log('get server info error');
} }
}, }
}),
getMetrics: builder.query({
query: () => ({ url: `/admin/system/metrics` }),
}),
getFirebaseConfig: builder.query({
query: () => ({ url: `admin/fcm/config` }),
}),
sendTestEmail: builder.mutation({
query: (data) => ({
url: `/admin/system/send_mail`,
method: "POST",
body: data,
}),
}),
updateFirebaseConfig: builder.mutation({
query: (data) => ({
url: `admin/fcm/config`,
method: "POST",
body: data,
}),
}),
getAgoraConfig: builder.query({
query: () => ({ url: `admin/agora/config` }),
}),
updateAgoraConfig: builder.mutation({
query: (data) => ({
url: `admin/agora/config`,
method: "POST",
body: data,
}),
}),
getSMTPConfig: builder.query({
query: () => ({ url: `admin/smtp/config` }),
}),
updateSMTPConfig: builder.mutation({
query: (data) => ({
url: `admin/smtp/config`,
method: "POST",
body: data,
}),
}),
updateLogo: builder.mutation({
query: (data) => ({
headers: {
"content-type": "image/png",
},
url: `admin/system/organization/logo`,
method: "POST",
body: data,
}),
async onQueryStarted(data, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
dispatch(
updateInfo({
logo: `${BASE_URL}/resource/organization/logo?t=${new Date().getTime()}`,
})
);
} catch {
console.log("update server logo error");
}
},
}),
createInviteLink: builder.query({
query: (expired_in = defaultExpireDuration) => ({
headers: {
"content-type": "text/plain",
accept: "text/plain",
},
url: `/admin/user/create_invite_link?expired_in=${expired_in}`,
responseHandler: (response) => response.text(),
}),
transformResponse: (link) => {
// 替换掉域名
const invite = new URL(link);
return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`;
},
async onQueryStarted(
expire = defaultExpireDuration,
{ dispatch, queryFulfilled }
) {
try {
const { data: link } = await queryFulfilled;
console.log("link", link);
dispatch(updateInviteLink({ expire, link }));
} catch {
console.log("invite link error");
}
},
}),
updateServer: builder.mutation({
query: (data) => ({
url: `admin/system/organization`,
method: "POST",
body: data,
}),
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
const { name: prevName, description: prevDesc } = getState().server;
dispatch(updateInfo(data));
try {
await queryFulfilled;
} catch {
dispatch(updateInfo({ name: prevName, description: prevDesc }));
}
},
}),
}), }),
getMetrics: builder.query({
query: () => ({ url: `/admin/system/metrics` })
}),
getFirebaseConfig: builder.query({
query: () => ({ url: `admin/fcm/config` })
}),
sendTestEmail: builder.mutation({
query: (data) => ({
url: `/admin/system/send_mail`,
method: 'POST',
body: data
})
}),
updateFirebaseConfig: builder.mutation({
query: (data) => ({
url: `admin/fcm/config`,
method: 'POST',
body: data
})
}),
getAgoraConfig: builder.query({
query: () => ({ url: `admin/agora/config` })
}),
updateAgoraConfig: builder.mutation({
query: (data) => ({
url: `admin/agora/config`,
method: 'POST',
body: data
})
}),
getSMTPConfig: builder.query({
query: () => ({ url: `admin/smtp/config` })
}),
updateSMTPConfig: builder.mutation({
query: (data) => ({
url: `admin/smtp/config`,
method: 'POST',
body: data
})
}),
getLoginConfig: builder.query({
query: () => ({ url: `admin/login/config` })
}),
updateLoginConfig: builder.mutation({
query: (data) => ({
url: `admin/login/config`,
method: 'POST',
body: data
})
}),
updateLogo: builder.mutation({
query: (data) => ({
headers: {
'content-type': 'image/png'
},
url: `admin/system/organization/logo`,
method: 'POST',
body: data
}),
async onQueryStarted(data, { dispatch, queryFulfilled }) {
try {
await queryFulfilled;
dispatch(
updateInfo({
logo: `${BASE_URL}/resource/organization/logo?t=${new Date().getTime()}`
})
);
} catch {
console.log('update server logo error');
}
}
}),
createInviteLink: builder.query({
query: (expired_in = defaultExpireDuration) => ({
headers: {
'content-type': 'text/plain',
accept: 'text/plain'
},
url: `/admin/user/create_invite_link?expired_in=${expired_in}`,
responseHandler: (response) => response.text()
}),
transformResponse: (link) => {
// 替换掉域名
const invite = new URL(link);
return `${location.origin}${invite.pathname}${invite.search}${invite.hash}`;
},
async onQueryStarted(expire = defaultExpireDuration, { dispatch, queryFulfilled }) {
try {
const { data: link } = await queryFulfilled;
console.log('link', link);
dispatch(updateInviteLink({ expire, link }));
} catch {
console.log('invite link error');
}
}
}),
updateServer: builder.mutation({
query: (data) => ({
url: `admin/system/organization`,
method: 'POST',
body: data
}),
async onQueryStarted(data, { dispatch, queryFulfilled, getState }) {
const { name: prevName, description: prevDesc } = getState().server;
dispatch(updateInfo(data));
try {
await queryFulfilled;
} catch {
dispatch(updateInfo({ name: prevName, description: prevDesc }));
}
}
})
})
}); });
export const { export const {
useSendTestEmailMutation, useSendTestEmailMutation,
useUpdateFirebaseConfigMutation, useUpdateFirebaseConfigMutation,
useGetFirebaseConfigQuery, useGetFirebaseConfigQuery,
useGetSMTPConfigQuery, useGetLoginConfigQuery,
useUpdateSMTPConfigMutation, useUpdateLoginConfigMutation,
useGetAgoraConfigQuery, useGetSMTPConfigQuery,
useUpdateAgoraConfigMutation, useUpdateSMTPConfigMutation,
useGetServerQuery, useGetAgoraConfigQuery,
useLazyGetMetricsQuery, useUpdateAgoraConfigMutation,
useLazyGetServerQuery, useGetServerQuery,
useUpdateServerMutation, useLazyGetMetricsQuery,
useUpdateLogoMutation, useLazyGetServerQuery,
useLazyCreateInviteLinkQuery, useUpdateServerMutation,
useUpdateLogoMutation,
useLazyCreateInviteLinkQuery
} = serverApi; } = serverApi;
@@ -0,0 +1,115 @@
// import { useState, useEffect } from "react";
import StyledContainer from './StyledContainer';
// import Input from "../../styled/Input";
import Textarea from '../../styled/Textarea';
import Toggle from '../../styled/Toggle';
import Label from '../../styled/Label';
import SaveTip from '../../SaveTip';
import useConfig from './useConfig';
export default function Logins() {
const { values, updateConfig, setValues, reset, changed } = useConfig('login');
const handleUpdate = () => {
// const { token_url, description } = values;
updateConfig(values);
};
const handleChange = (evt) => {
const newValue = evt.target.value;
const { type } = evt.target.dataset;
const items = newValue ? newValue.split('\n') : [];
setValues((prev) => {
return { ...prev, [type]: items };
});
};
const handleToggle = (val) => {
setValues((prev) => {
return { ...prev, ...val };
});
};
if (!values) return null;
const { google, metamask, password, oidc = [] } = values ?? {};
return (
<StyledContainer>
<div className="inputs">
<div className="input row">
<Label>Password</Label>
<Toggle
onClick={handleToggle.bind(null, { password: !password })}
data-checked={password}
></Toggle>
</div>
<div className="input row">
<Label>Google</Label>
<Toggle onClick={handleToggle.bind(null, { google: !google })} data-checked={google}></Toggle>
</div>
<div className="input row">
<Label>Metamask</Label>
<Toggle
onClick={handleToggle.bind(null, { metamask: !metamask })}
data-checked={metamask}
></Toggle>
</div>
<div className="input">
<Label htmlFor="desc">OIDC</Label>
<Textarea
rows={10}
data-type="oidc"
onChange={handleChange}
value={oidc.join('\n')}
name="oidc"
placeholder="Input issuer list, one line, one issuer"
/>
</div>
{/* <div className="input">
<Label htmlFor="name">Token Url</Label>
<Input
disabled={!enabled}
data-type="token_url"
onChange={handleChange}
value={token_url || "https://oauth2.googleapis.com/token"}
name="token_url"
placeholder="Token URL"
/>
</div>
<div className="input">
<Label htmlFor="desc">Project ID</Label>
<Input
disabled={!enabled}
type={"number"}
data-type="project_id"
onChange={handleChange}
value={project_id}
name="project_id"
placeholder="Project ID"
/>
</div>
<div className="input">
<Label htmlFor="desc">Private Key</Label>
<Textarea
rows={10}
disabled={!enabled}
data-type="private_key"
onChange={handleChange}
value={private_key}
name="private_key"
placeholder="Private key"
/>
</div>
<div className="input">
<Label htmlFor="desc">Client Email</Label>
<Input
disabled={!enabled}
data-type="client_email"
onChange={handleChange}
value={client_email}
name="client_email"
placeholder="Client Email address"
/>
</div> */}
</div>
{changed && <SaveTip saveHandler={handleUpdate} resetHandler={reset} />}
{/* <button onClick={handleUpdate} className="btn">update</button> */}
</StyledContainer>
);
}
@@ -1,96 +1,92 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from 'react';
import { isObjectEqual } from "../../../utils"; import { isObjectEqual } from '../../../utils';
import toast from "react-hot-toast"; import toast from 'react-hot-toast';
import { import {
useGetAgoraConfigQuery, useGetAgoraConfigQuery,
useGetFirebaseConfigQuery, useGetFirebaseConfigQuery,
useGetSMTPConfigQuery, useGetSMTPConfigQuery,
useUpdateSMTPConfigMutation, useGetLoginConfigQuery,
useUpdateAgoraConfigMutation, useUpdateLoginConfigMutation,
useUpdateFirebaseConfigMutation, useUpdateSMTPConfigMutation,
} from "../../../../app/services/server"; useUpdateAgoraConfigMutation,
export default function useConfig(config = "smtp") { useUpdateFirebaseConfigMutation
const [changed, setChanged] = useState(false); } from '../../../../app/services/server';
const [values, setValues] = useState({}); export default function useConfig(config = 'smtp') {
const { data: SMTP, refetch: refetchSMTP } = useGetSMTPConfigQuery(); const [changed, setChanged] = useState(false);
const [ const [values, setValues] = useState({});
updateSMTPConfig, const { data: Login, refetch: refetchLogin } = useGetLoginConfigQuery();
{ isSuccess: SMTPUpdated }, const [updateLoginConfig, { isSuccess: LoginUpdated }] = useUpdateLoginConfigMutation();
] = useUpdateSMTPConfigMutation(); const { data: SMTP, refetch: refetchSMTP } = useGetSMTPConfigQuery();
const { data: Agora, refetch: refetchAgora } = useGetAgoraConfigQuery(); const [updateSMTPConfig, { isSuccess: SMTPUpdated }] = useUpdateSMTPConfigMutation();
const [ const { data: Agora, refetch: refetchAgora } = useGetAgoraConfigQuery();
updateAgoraConfig, const [updateAgoraConfig, { isSuccess: AgoraUpdated }] = useUpdateAgoraConfigMutation();
{ isSuccess: AgoraUpdated }, const { data: Firebase, refetch: refetchFirebase } = useGetFirebaseConfigQuery();
] = useUpdateAgoraConfigMutation(); const [updateFirebaseConfig, { isSuccess: FirebaseUpdated }] = useUpdateFirebaseConfigMutation();
const {
data: Firebase,
refetch: refetchFirebase,
} = useGetFirebaseConfigQuery();
const [
updateFirebaseConfig,
{ isSuccess: FirebaseUpdated },
] = useUpdateFirebaseConfigMutation();
const datas = { const datas = {
smtp: SMTP, login: Login,
agora: Agora, smtp: SMTP,
firebase: Firebase, agora: Agora,
}; firebase: Firebase
const updateFns = { };
smtp: updateSMTPConfig, const updateFns = {
agora: updateAgoraConfig, login: updateLoginConfig,
firebase: updateFirebaseConfig, smtp: updateSMTPConfig,
}; agora: updateAgoraConfig,
const refetchs = { firebase: updateFirebaseConfig
smtp: refetchSMTP, };
agora: refetchAgora, const refetchs = {
firebase: refetchFirebase, smtp: refetchSMTP,
}; agora: refetchAgora,
const updateds = { firebase: refetchFirebase,
smtp: SMTPUpdated, login: refetchLogin
agora: AgoraUpdated, };
firebase: FirebaseUpdated, const updateds = {
}; login: LoginUpdated,
const data = datas[config]; smtp: SMTPUpdated,
const updateConfig = updateFns[config]; agora: AgoraUpdated,
const refetch = refetchs[config]; firebase: FirebaseUpdated
const updated = updateds[config]; };
const reset = () => { const data = datas[config];
setValues(data ?? {}); const updateConfig = updateFns[config];
}; const refetch = refetchs[config];
const updated = updateds[config];
const reset = () => {
setValues(data ?? {});
};
const toggleEnable = () => { const toggleEnable = () => {
setValues((prev) => { setValues((prev) => {
return { ...prev, enabled: !prev.enabled }; return { ...prev, enabled: !prev.enabled };
}); });
}; };
useEffect(() => { useEffect(() => {
if (updated) { if (updated) {
toast.success("Configuration Updated!"); toast.success('Configuration Updated!');
refetch(); refetch();
} }
}, [updated]); }, [updated]);
useEffect(() => { useEffect(() => {
console.log("wtf", data); console.log('wtf', data);
// if (data) { // if (data) {
setValues(data ?? {}); setValues(data ?? {});
// } // }
}, [data]); }, [data]);
useEffect(() => { useEffect(() => {
// if (data && values) { // if (data && values) {
if (!isObjectEqual(data, values)) { if (!isObjectEqual(data, values)) {
setChanged(true); setChanged(true);
} else { } else {
setChanged(false); setChanged(false);
} }
// } // }
}, [data, values]); }, [data, values]);
return { return {
reset, reset,
changed, changed,
updateConfig, updateConfig,
values, values,
setValues, setValues,
toggleEnable, toggleEnable
}; };
} }
+98 -92
View File
@@ -1,98 +1,104 @@
import { useSelector } from "react-redux"; import { useSelector } from 'react-redux';
import MyAccount from "./MyAccount"; import MyAccount from './MyAccount';
import Overview from "./Overview"; import Overview from './Overview';
import ConfigFirebase from "./config/Firebase"; import Logins from './config/Logins';
import ConfigSMTP from "./config/SMTP"; import ConfigFirebase from './config/Firebase';
import Notifications from "./Notifications"; import ConfigSMTP from './config/SMTP';
import ManageMembers from "../ManageMembers"; import Notifications from './Notifications';
import FAQ from "../FAQ"; import ManageMembers from '../ManageMembers';
import ConfigAgora from "./config/Agora"; import FAQ from '../FAQ';
import ConfigAgora from './config/Agora';
const navs = [ const navs = [
{ {
title: "General", title: 'General',
items: [ items: [
{ {
name: "overview", name: 'overview',
title: "Overview", title: 'Overview',
component: <Overview />, component: <Overview />
}, },
{ {
name: "members", name: 'members',
title: "Members", title: 'Members',
component: <ManageMembers />, component: <ManageMembers />,
admin: true, admin: true
}, },
{ {
name: "notification", name: 'notification',
title: "Notification", title: 'Notification',
component: <Notifications />, component: <Notifications />
}, }
], ]
}, },
{ {
title: "User", title: 'User',
items: [ items: [
{ {
name: "my_account", name: 'my_account',
title: "My Account", title: 'My Account',
component: <MyAccount />, component: <MyAccount />
}, }
], ]
}, },
{ {
title: "Configuration", title: 'Configuration',
items: [ items: [
{ {
name: "firebase", name: 'firebase',
title: "Firebase", title: 'Firebase',
component: <ConfigFirebase />, component: <ConfigFirebase />
}, },
{ {
name: "agora", name: 'agora',
title: "Agora", title: 'Agora',
component: <ConfigAgora />, component: <ConfigAgora />
}, },
{ {
name: "smtp", name: 'smtp',
title: "SMTP", title: 'SMTP',
component: <ConfigSMTP />, component: <ConfigSMTP />
}, },
], {
admin: true, name: 'social_login',
}, title: 'Social Login',
{ component: <Logins />
title: "About", }
items: [ ],
{ admin: true
name: "faq", },
title: "FAQ", {
component: <FAQ />, title: 'About',
}, items: [
{ {
name: "terms", name: 'faq',
title: "Terms & Privacy", title: 'FAQ',
component: "Terms & Privacy", component: <FAQ />
}, },
{ {
name: "feedback", name: 'terms',
title: "Feedback", title: 'Terms & Privacy',
component: "feedback", component: 'Terms & Privacy'
}, },
], {
}, name: 'feedback',
title: 'Feedback',
component: 'feedback'
}
]
}
]; ];
const useNavs = () => { const useNavs = () => {
const loginUser = useSelector((store) => { const loginUser = useSelector((store) => {
return store.contacts.byId[store.authData.uid]; return store.contacts.byId[store.authData.uid];
}); });
const Navs = navs.filter((nav) => { const Navs = navs.filter((nav) => {
if (loginUser.is_admin) { if (loginUser.is_admin) {
return true; return true;
} else { } else {
return !nav.admin; return !nav.admin;
} }
}); });
return Navs; return Navs;
}; };
export default useNavs; export default useNavs;
+26 -30
View File
@@ -1,35 +1,31 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
import { useEffect } from "react"; import { useEffect } from 'react';
import { useGetOpenidMutation } from "../../app/services/auth"; import { useGetOpenidMutation } from '../../app/services/auth';
import solidSvg from "../../assets/icons/solid.svg?url"; import solidSvg from '../../assets/icons/solid.svg?url';
import { StyledSocialButton } from "./styled"; import { StyledSocialButton } from './styled';
export default function SolidLoginButton() { export default function SolidLoginButton({ issuers }) {
const [getOpenId, { data, isLoading, isSuccess }] = useGetOpenidMutation(); const [getOpenId, { data, isLoading, isSuccess }] = useGetOpenidMutation();
const handleSolidLogin = () => { const handleSolidLogin = () => {
getOpenId({ getOpenId({
// issuer: "solidweb.org", // issuer: "solidweb.org",
issuer: "broker.pod.inrupt.com", issuer: issuers[0],
redirect_uri: `${location.origin}/#/login`, redirect_uri: `${location.origin}/#/login`
}); });
}; };
useEffect(() => { useEffect(() => {
if (isSuccess) { if (isSuccess) {
console.log("wtf", data); console.log('wtf', data);
const { url } = data; const { url } = data;
location.href = url; location.href = url;
} }
}, [data, isSuccess]); }, [data, isSuccess]);
return ( return (
<StyledSocialButton <StyledSocialButton disabled={isLoading} onClick={handleSolidLogin} href="#">
disabled={isLoading} <img src={solidSvg} className="icon" alt="solid icon" />
onClick={handleSolidLogin} {isLoading ? `Redirecting...` : `Sign in with Solid`}
href="#" </StyledSocialButton>
> );
<img src={solidSvg} className="icon" alt="solid icon" />
{isLoading ? `Redirecting...` : `Sign in with Solid`}
</StyledSocialButton>
);
} }
+123 -123
View File
@@ -1,130 +1,130 @@
/* eslint-disable no-undef */ /* eslint-disable no-undef */
import { useState, useEffect } from "react"; import { useState, useEffect } from 'react';
import { useDispatch } from "react-redux"; import { useDispatch } from 'react-redux';
import { useNavigate } from "react-router-dom"; import { useNavigate } from 'react-router-dom';
import toast from "react-hot-toast"; import toast from 'react-hot-toast';
import BASE_URL from "../../app/config"; import BASE_URL from '../../app/config';
// import web3 from "web3"; // import web3 from "web3";
import StyledWrapper from "./styled"; import StyledWrapper from './styled';
import MetamaskLoginButton from "./MetamaskLoginButton"; import MetamaskLoginButton from './MetamaskLoginButton';
import SolidLoginButton from "./SolidLoginButton"; import SolidLoginButton from './SolidLoginButton';
import Input from "../../common/component/styled/Input"; import Input from '../../common/component/styled/Input';
import Button from "../../common/component/styled/Button"; import Button from '../../common/component/styled/Button';
import GoogleLoginButton from "./GoogleLoginButton"; import GoogleLoginButton from './GoogleLoginButton';
import { useLoginMutation } from "../../app/services/auth"; import { useLoginMutation } from '../../app/services/auth';
import { setAuthData } from "../../app/slices/auth.data"; import { useGetLoginConfigQuery } from '../../app/services/server';
import { setAuthData } from '../../app/slices/auth.data';
export default function LoginPage() { export default function LoginPage() {
const [login, { data, isSuccess, isLoading, error }] = useLoginMutation(); const [login, { data, isSuccess, isLoading, error }] = useLoginMutation();
const navigateTo = useNavigate(); const { data: loginConfig, isSuccess: loginConfigSuccess } = useGetLoginConfigQuery();
const dispatch = useDispatch(); const navigateTo = useNavigate();
const [input, setInput] = useState({ const dispatch = useDispatch();
email: "", const [input, setInput] = useState({
password: "", email: '',
password: ''
});
useEffect(() => {
const query = new URLSearchParams(location.search);
const code = query.get('code');
const state = query.get('state');
if (code && state) {
login({
code,
state,
type: 'oidc'
});
}
}, []);
useEffect(() => {
if (error) {
console.log(error);
switch (error.status) {
case 'PARSING_ERROR':
toast.error(error.data);
break;
case 401:
toast.error('username or password incorrect');
break;
case 404:
toast.error('account not exsit');
break;
default:
toast.error('something error');
break;
}
return;
}
}, [error]);
useEffect(() => {
if (isSuccess && data) {
// 更新本地认证信息
console.log('login data', data);
toast.success('login success');
dispatch(setAuthData(data));
navigateTo('/');
}
}, [isSuccess, data]);
const handleLogin = (evt) => {
evt.preventDefault();
console.log('wtf', input);
login({
...input,
type: 'password'
}); });
useEffect(() => { };
const query = new URLSearchParams(location.search);
const code = query.get("code");
const state = query.get("state");
if (code && state) {
login({
code,
state,
type: "oidc",
});
}
}, []);
useEffect(() => { const handleInput = (evt) => {
if (error) { const { type } = evt.target.dataset;
console.log(error); const { value } = evt.target;
switch (error.status) { console.log(type, value);
case "PARSING_ERROR": setInput((prev) => {
toast.error(error.data); prev[type] = value;
break; return { ...prev };
case 401: });
toast.error("username or password incorrect"); };
break; const { email, password } = input;
case 404: if (!loginConfigSuccess) return null;
toast.error("account not exsit"); const { google: enableGoogleLogin, metamask: enableMetamaskLogin, oidc } = loginConfig;
break; return (
default: <StyledWrapper>
toast.error("something error"); <div className="form">
break; <div className="tips">
} <img src={`${BASE_URL}/resource/organization/logo`} alt="logo" className="logo" />
return; <h2 className="title">Login to Rustchat</h2>
} <span className="desc">Please enter your details.</span>
}, [error]); </div>
useEffect(() => { <form onSubmit={handleLogin}>
if (isSuccess && data) { <Input
// 更新本地认证信息 className="large"
console.log("login data", data); name="email"
toast.success("login success"); value={email}
dispatch(setAuthData(data)); required
navigateTo("/"); placeholder="Enter your email"
} data-type="email"
}, [isSuccess, data]); onChange={handleInput}
/>
const handleLogin = (evt) => { <Input
evt.preventDefault(); className="large"
console.log("wtf", input); type="password"
login({ value={password}
...input, name="password"
type: "password", required
}); data-type="password"
}; onChange={handleInput}
placeholder="Enter your password"
const handleInput = (evt) => { />
const { type } = evt.target.dataset; <Button type="submit" disabled={isLoading}>
const { value } = evt.target; {isLoading ? 'Signing' : `Sign in`}
console.log(type, value); </Button>
setInput((prev) => { </form>
prev[type] = value; {(enableGoogleLogin || enableMetamaskLogin || oidc.length > 0) && <hr className="or" />}
return { ...prev }; {enableGoogleLogin && <GoogleLoginButton login={login} />}
}); {enableMetamaskLogin && <MetamaskLoginButton login={login} />}
}; {oidc.length > 0 && <SolidLoginButton issuers={oidc} />}
const { email, password } = input; </div>
return ( </StyledWrapper>
<StyledWrapper> );
<div className="form">
<div className="tips">
<img
src={`${BASE_URL}/resource/organization/logo`}
alt="logo"
className="logo"
/>
<h2 className="title">Login to Rustchat</h2>
<span className="desc">Please enter your details.</span>
</div>
<form onSubmit={handleLogin}>
<Input
className="large"
name="email"
value={email}
required
placeholder="Enter your email"
data-type="email"
onChange={handleInput}
/>
<Input
className="large"
type="password"
value={password}
name="password"
required
data-type="password"
onChange={handleInput}
placeholder="Enter your password"
/>
<Button type="submit" disabled={isLoading}>
{isLoading ? "Signing" : `Sign in`}
</Button>
</form>
<hr className="or" />
<GoogleLoginButton login={login} />
<MetamaskLoginButton login={login} />
<SolidLoginButton />
</div>
</StyledWrapper>
);
} }