feat: implement OIDC settings

This commit is contained in:
Liyang Zhu
2022-06-12 00:58:05 +08:00
parent cf09bfea3b
commit 585dc59a1d
10 changed files with 243 additions and 120 deletions
+9 -6
View File
@@ -62,17 +62,20 @@ const StyledForm = styled.form`
}
`;
const VALUE_NOT_SET = {};
const VALUES_NOT_SET = {};
export default function Radio({
options,
values = undefined,
value = undefined,
values = VALUES_NOT_SET,
value = VALUE_NOT_SET,
defaultValue = undefined,
onChange = undefined
}) {
const id = useId();
const [fallbackValue, setFallbackValue] = useState(defaultValue);
const _value = value !== undefined ? value : fallbackValue;
const _value = value !== VALUE_NOT_SET ? value : fallbackValue;
return (
<StyledForm>
@@ -80,11 +83,11 @@ export default function Radio({
<div className="option" key={index}>
<input
type="radio"
checked={(values !== undefined ? values.indexOf(_value) : _value) === index}
checked={(values !== VALUES_NOT_SET ? values.indexOf(_value) : _value) === index}
onChange={() => {
const valueToSet = values === undefined ? index : values[index];
const valueToSet = values === VALUES_NOT_SET ? index : values[index];
// Set fallback value if not in controlled mode
if (value === undefined) {
if (value === VALUE_NOT_SET) {
setFallbackValue(valueToSet);
}
// Invoke `onChange` handler if defined
+7 -2
View File
@@ -24,7 +24,10 @@ const Styled = styled.div`
height: 20px !important;
}
`;
export default function Select({ options = [], updateSelect = null }) {
const CURRENT_NOT_SET = {};
export default function Select({ options = [], updateSelect = null, current = CURRENT_NOT_SET }) {
const [optionsVisible, setOptionsVisible] = useState(false);
const [curr, setCurr] = useState(undefined);
const toggleVisible = () => {
@@ -62,7 +65,9 @@ export default function Select({ options = [], updateSelect = null }) {
}
>
<Styled onClick={toggleVisible}>
<span className="txt">{curr?.title || `Select`}</span>
<span className="txt">
{(current !== CURRENT_NOT_SET ? current : curr)?.title || `Select`}
</span>
<IconArrow className="icon" />
</Styled>
</Tippy>
+2 -6
View File
@@ -10,12 +10,8 @@ export const isImage = (file_type = "", size = 0) => {
return file_type.startsWith("image") && size <= FILE_IMAGE_SIZE;
};
export const isObjectEqual = (obj1, obj2) => {
let o1 = Object.entries(obj1 ?? {})
.sort()
.toString();
let o2 = Object.entries(obj2 ?? {})
.sort()
.toString();
let o1 = JSON.stringify(obj1 ?? {});
let o2 = JSON.stringify(obj2 ?? {});
return o1 === o2;
};
export const isTreatAsImage = (file) => {
+65
View File
@@ -0,0 +1,65 @@
/* eslint-disable no-undef */
import { useState } from "react";
import styled from "styled-components";
import StyledModal from "../../common/component/styled/Modal";
import Modal from "../../common/component/Modal";
import { StyledSocialButton } from "./styled";
import solidSvg from "../../assets/icons/solid.svg?url";
import StyledButton from "../../common/component/styled/Button";
import SolidLoginButton from "./SolidLoginButton";
const StyledOicdLoginModal = styled(StyledModal)`
text-align: center;
padding: 32px 32px 16px;
> *:first-child {
margin-bottom: 32px;
}
> .button {
> .icon {
width: 24px;
height: 24px;
}
&Cancel {
color: #8f8f8f;
}
}
`;
export default function OidcLoginButton({ issuers }) {
const [modal, setModal] = useState(true);
if (!issuers) return null;
return (
<>
<StyledSocialButton
onClick={() => {
setModal(true);
}}
>
<img src={solidSvg} className="icon" alt="solid icon" />
Sign in with OIDC
</StyledSocialButton>
{modal && (
<Modal id="modal-modal">
<StyledOicdLoginModal title="Login with OIDC">
{issuers
.filter((issuer) => issuer.enable)
.map((issuer, index) => (
<SolidLoginButton issuer={issuer} key={index} />
))}
<StyledButton
className="border_less ghost buttonCancel"
onClick={() => {
setModal(false);
}}
>
Close
</StyledButton>
</StyledOicdLoginModal>
</Modal>
)}
</>
);
}
+4 -5
View File
@@ -1,16 +1,15 @@
/* eslint-disable no-undef */
import { useEffect } from "react";
import { useGetOpenidMutation } from "../../app/services/auth";
import solidSvg from "../../assets/icons/solid.svg?url";
import { StyledSocialButton } from "./styled";
export default function SolidLoginButton({ issuers }) {
export default function SolidLoginButton({ issuer }) {
const [getOpenId, { data, isLoading, isSuccess }] = useGetOpenidMutation();
const handleSolidLogin = () => {
getOpenId({
// issuer: "solidweb.org",
issuer: issuers[0],
issuer,
redirect_uri: `${location.origin}/#/login`
});
};
@@ -24,8 +23,8 @@ export default function SolidLoginButton({ issuers }) {
return (
<StyledSocialButton disabled={isLoading} onClick={handleSolidLogin}>
<img src={solidSvg} className="icon" alt="solid icon" />
{isLoading ? `Redirecting...` : `Sign in with Solid`}
<img src={issuer.favicon} className="icon" alt="icon" />
Login with {issuer.domain}
</StyledSocialButton>
);
}
+2 -3
View File
@@ -5,7 +5,7 @@ import BASE_URL from "../../app/config";
// import web3 from "web3";
import StyledWrapper from "./styled";
import MetamaskLoginButton from "./MetamaskLoginButton";
import SolidLoginButton from "./SolidLoginButton";
import OidcLoginButton from "./OidcLoginButton";
import Input from "../../common/component/styled/Input";
import Button from "../../common/component/styled/Button";
import GoogleLoginButton from "./GoogleLoginButton";
@@ -16,7 +16,6 @@ import { useGetLoginConfigQuery, useGetSMTPStatusQuery } from "../../app/service
import useGoogleAuthConfig from "../../common/hook/useGoogleAuthConfig";
import GithubLoginButton from "./GithubLoginButton";
import useGithubAuthConfig from "../../common/hook/useGithubAuthConfig";
export default function LoginPage() {
const { data: enableSMTP } = useGetSMTPStatusQuery();
const [login, { isSuccess, isLoading, error }] = useLoginMutation();
@@ -171,7 +170,7 @@ export default function LoginPage() {
{googleLogin && <GoogleLoginButton login={login} clientId={clientId} />}
{enableGithubLogin && <GithubLoginButton config={githubAuthConfig} />}
{enableMetamaskLogin && <MetamaskLoginButton login={login} />}
{oidc.length > 0 && <SolidLoginButton issuers={oidc} />}
{oidc.length > 0 && <OidcLoginButton issuers={oidc} />}
{whoCanSignUp === "EveryOne" && <SignUpLink />}
</div>
</StyledWrapper>
+49 -8
View File
@@ -8,13 +8,16 @@ import Styled from "./styled";
// import IconPlus from "../../../../assets/icons/plus.circle.svg";
import IconMinus from "../../../../assets/icons/minus.circle.svg";
export default function IssuerList({ issuers = [] }) {
const [select, setSelect] = useState(undefined);
export default function IssuerList({ issuers = [], onChange }) {
const [select, setSelect] = useState({});
const [newDomain, setNewDomain] = useState("");
const handleNewDomain = (evt) => {
setNewDomain(evt.target.value);
};
const disableBtn = !(newDomain || !!select?.value);
const disableBtn =
(!newDomain && !select?.value) ||
!select?.title ||
issuers.some((issuer) => issuer.domain === newDomain);
return (
<Styled>
<ul className="issuers">
@@ -22,9 +25,14 @@ export default function IssuerList({ issuers = [] }) {
return (
<li key={domain} className="issuer">
<div className="left">
<IconMinus className="remove" />
<IconMinus
className="remove"
onClick={() => {
onChange(issuers.filter((issuer) => issuer.domain !== domain));
}}
/>
<div className="data">
<img src={favicon} alt="logo" className="icon" />
{Boolean(favicon) && <img src={favicon} alt="logo" className="icon" />}
<Input
readOnly
value={domain}
@@ -35,7 +43,17 @@ export default function IssuerList({ issuers = [] }) {
</div>
</div>
<div className="right">
<Toggle data-checked={enable} />
<Toggle
data-checked={enable}
onClick={() => {
onChange(
issuers.map((issuer) => ({
...issuer,
enable: issuer.domain === domain ? !enable : issuer.enable
}))
);
}}
/>
</div>
</li>
);
@@ -43,7 +61,14 @@ export default function IssuerList({ issuers = [] }) {
<li className="issuer add">
<div className="left">
<Select options={options} updateSelect={setSelect} />
<Select
options={options.map((option) => ({
...option,
selected: issuers.some((issuer) => issuer.domain === option.value)
}))}
current={select}
updateSelect={setSelect}
/>
<div className="data">
<Input
onChange={handleNewDomain}
@@ -56,7 +81,23 @@ export default function IssuerList({ issuers = [] }) {
</div>
</div>
<div className="right">
<Button disabled={disableBtn}>Add</Button>
<Button
disabled={disableBtn}
onClick={() => {
const { icon, value } = options.find((option) => option.value === select.value);
onChange(
issuers.concat({
enable: true,
favicon: icon || "",
domain: value || newDomain
})
);
setSelect({});
setNewDomain("");
}}
>
Add
</Button>
</div>
</li>
</ul>
File diff suppressed because one or more lines are too long
@@ -18,14 +18,13 @@ const Styled = styled.div`
flex: 1;
display: flex;
align-items: center;
gap: 16px;
justify-content: space-between;
.remove {
cursor: pointer;
}
.data {
display: flex;
align-items: flex-end;
align-items: center;
gap: 16px;
justify-content: space-between;
> .icon {
+9 -1
View File
@@ -182,7 +182,15 @@ export default function Logins() {
</div>
</div>
<div className="row">
<IssuerList issuers={oidc} />
<IssuerList
issuers={oidc}
onChange={(newOidc) => {
setValues((prev) => ({
...prev,
oidc: newOidc
}));
}}
/>
</div>
</div>
</div>