Skip to content

Commit 77e3423

Browse files
implemented new auth flow
1 parent 861e472 commit 77e3423

File tree

5 files changed

+256
-5
lines changed

5 files changed

+256
-5
lines changed

client/packages/lowcoder-design/src/components/tacoInput.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,8 +335,9 @@ const FormInput = (props: {
335335
className?: string;
336336
inputRef?: Ref<InputRef>;
337337
msg?: string;
338+
defaultValue?: string;
338339
}) => {
339-
const { mustFill, checkRule, label, placeholder, onChange, formName, className, inputRef } =
340+
const { mustFill, checkRule, label, placeholder, onChange, formName, className, inputRef, defaultValue } =
340341
props;
341342
const [valueValid, setValueValid] = useState(true);
342343
return (
@@ -350,6 +351,7 @@ const FormInput = (props: {
350351
ref={inputRef}
351352
name={formName}
352353
placeholder={placeholder}
354+
defaultValue={defaultValue}
353355
onChange={(e) => {
354356
let valid = true;
355357
if (checkRule) {

client/packages/lowcoder/src/api/orgApi.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export class OrgApi extends Api {
5252
static deleteOrgURL = (orgId: string) => `/organizations/${orgId}`;
5353
static updateOrgURL = (orgId: string) => `/organizations/${orgId}/update`;
5454
static fetchUsage = (orgId: string) => `/organizations/${orgId}/api-usage`;
55+
static fetchOrgsByEmailURL = (email: string) => `organizations/byuser/${email}`;
5556

5657
static createGroup(request: { name: string }): AxiosPromise<GenericApiResponse<OrgGroup>> {
5758
return Api.post(OrgApi.createGroupURL, request);
@@ -141,6 +142,9 @@ export class OrgApi extends Api {
141142
return Api.get(OrgApi.fetchUsage(orgId), { lastMonthOnly: true });
142143
}
143144

145+
static fetchOrgsByEmail(email: string): AxiosPromise<ApiResponse> {
146+
return Api.get(OrgApi.fetchOrgsByEmailURL(email));
147+
}
144148
}
145149

146150
export default OrgApi;

client/packages/lowcoder/src/pages/userAuth/formLogin.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ import { Link, useLocation, useParams } from "react-router-dom";
2020
import { Divider } from "antd";
2121
import Flex from "antd/es/flex";
2222

23-
const AccountLoginWrapper = styled(FormWrapperMobile)`
23+
export const AccountLoginWrapper = styled(FormWrapperMobile)`
24+
position: relative;
2425
display: flex;
2526
flex-direction: column;
2627
margin-bottom: 0px;
@@ -62,7 +63,6 @@ export default function FormLogin(props: FormLoginProps) {
6263

6364
return (
6465
<>
65-
{/* <LoginCardTitle>{trans("userAuth.login")}</LoginCardTitle> */}
6666
<AccountLoginWrapper>
6767
<FormInput
6868
className="form-input"
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import { FormInput, messageInstance, PasswordInput } from "lowcoder-design";
2+
import {
3+
AuthBottomView,
4+
ConfirmButton,
5+
FormWrapperMobile,
6+
LoginCardTitle,
7+
StyledRouteLink,
8+
} from "pages/userAuth/authComponents";
9+
import React, { useContext, useMemo, useState } from "react";
10+
import styled from "styled-components";
11+
import UserApi from "api/userApi";
12+
import { useRedirectUrl } from "util/hooks";
13+
import { checkEmailValid, checkPhoneValid } from "util/stringUtils";
14+
import { UserConnectionSource } from "@lowcoder-ee/constants/userConstants";
15+
import { trans } from "i18n";
16+
import { AuthContext, useAuthSubmit } from "pages/userAuth/authUtils";
17+
import { ThirdPartyAuth } from "pages/userAuth/thirdParty/thirdPartyAuth";
18+
import { AUTH_FORGOT_PASSWORD_URL, AUTH_REGISTER_URL, ORG_AUTH_FORGOT_PASSWORD_URL, ORG_AUTH_REGISTER_URL } from "constants/routesURL";
19+
import { Link, useLocation, useParams } from "react-router-dom";
20+
import { Divider } from "antd";
21+
import Flex from "antd/es/flex";
22+
import { validateResponse } from "@lowcoder-ee/api/apiUtils";
23+
import OrgApi from "@lowcoder-ee/api/orgApi";
24+
import Card from "antd/es/card/Card";
25+
import { AccountLoginWrapper } from "./formLogin";
26+
import { default as Button } from "antd/es/button";
27+
import LeftOutlined from "@ant-design/icons/LeftOutlined";
28+
29+
const StyledCard = styled.div<{$selected: boolean}>`
30+
display: flex;
31+
justify-content: center;
32+
flex-direction: column;
33+
min-height: 56px;
34+
margin-bottom: -1px;
35+
padding: 0 24px;
36+
color: rgba(0, 0, 0, 0.88);
37+
font-size: 16px;
38+
background: transparent;
39+
border: 1px solid #f0f0f0;
40+
border-radius: 8px;
41+
cursor: pointer;
42+
margin-bottom: 16px;
43+
// box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03), 0 1px 6px -1px rgba(0, 0, 0, 0.02);
44+
${props => props.$selected && `background: #e6f4ff;`}
45+
46+
&:hover {
47+
box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.16), 0 3px 6px 0 rgba(0, 0, 0, 0.12), 0 5px 12px 4px rgba(0, 0, 0, 0.09);
48+
}
49+
`;
50+
51+
type OrgItem = {
52+
orgId: string;
53+
orgName: string;
54+
}
55+
56+
enum CurrentStepEnum {
57+
EMAIL = "EMAIL",
58+
WORKSPACES = "WORKSPACES",
59+
AUTH_PROVIDERS = "AUTH_PROVIDERS",
60+
}
61+
62+
const StepHeader = (props : {
63+
title: string,
64+
}) => (
65+
<Flex justify="center" style={{marginBottom: '22px'}}>
66+
<h3 style={{margin: 0}}>{props.title}</h3>
67+
</Flex>
68+
)
69+
70+
const StepBackButton = (props : {
71+
onClick: () => void,
72+
}) => (
73+
<Button
74+
type="link"
75+
icon={<LeftOutlined style={{fontSize: '12px'}} />}
76+
style={{
77+
position: 'absolute',
78+
padding: 0,
79+
}}
80+
onClick={props.onClick}
81+
>
82+
Back
83+
</Button>
84+
)
85+
export default function FormLoginSteps() {
86+
const [account, setAccount] = useState("");
87+
const [password, setPassword] = useState("");
88+
const redirectUrl = useRedirectUrl();
89+
const { systemConfig, inviteInfo, fetchUserAfterAuthSuccess } = useContext(AuthContext);
90+
const invitationId = inviteInfo?.invitationId;
91+
const authId = systemConfig?.form.id;
92+
const location = useLocation();
93+
const [orgLoading, setOrgLoading] = useState(false);
94+
const [orgList, setOrgList] = useState<OrgItem[]>([]);
95+
const [currentStep, setCurrentStep] = useState<CurrentStepEnum>(CurrentStepEnum.EMAIL);
96+
const [organizationId, setOrganizationId] = useState<string>();
97+
98+
const { onSubmit, loading } = useAuthSubmit(
99+
() =>
100+
UserApi.formLogin({
101+
register: false,
102+
loginId: account,
103+
password: password,
104+
invitationId: invitationId,
105+
source: UserConnectionSource.email,
106+
orgId: organizationId,
107+
authId,
108+
}),
109+
false,
110+
redirectUrl,
111+
fetchUserAfterAuthSuccess,
112+
);
113+
114+
const fetchOrgsByEmail = () => {
115+
setOrgLoading(true);
116+
OrgApi.fetchOrgsByEmail(account)
117+
.then((resp) => {
118+
if (validateResponse(resp)) {
119+
console.log(resp.data.data);
120+
setOrgList(resp.data.data);
121+
if (resp.data.data.length === 1) {
122+
setOrganizationId(resp.data.data[0].orgId);
123+
setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS);
124+
return;
125+
}
126+
setCurrentStep(CurrentStepEnum.WORKSPACES);
127+
} else {
128+
throw new Error('Error while fetching organizations');
129+
}
130+
})
131+
.catch((e) => {
132+
messageInstance.error(e.message);
133+
})
134+
.finally(() => {
135+
setOrgLoading(false);
136+
});
137+
}
138+
139+
if(currentStep === CurrentStepEnum.EMAIL) {
140+
return (
141+
<>
142+
<AccountLoginWrapper>
143+
<StepHeader title={trans("userAuth.inputEmail")} />
144+
<FormInput
145+
className="form-input"
146+
// label={trans("userAuth.email")}
147+
label={''}
148+
defaultValue={account}
149+
onChange={(value, valid) => setAccount(valid ? value : "")}
150+
placeholder={trans("userAuth.inputEmail")}
151+
checkRule={{
152+
check: (value) => checkPhoneValid(value) || checkEmailValid(value),
153+
errorMsg: trans("userAuth.inputValidEmail"),
154+
}}
155+
/>
156+
<ConfirmButton loading={orgLoading} disabled={!account} onClick={fetchOrgsByEmail}>
157+
{/* {trans("userAuth.login")} */}
158+
Continue
159+
</ConfirmButton>
160+
</AccountLoginWrapper>
161+
<Divider/>
162+
<AuthBottomView>
163+
<StyledRouteLink to={{
164+
pathname: AUTH_REGISTER_URL,
165+
state: location.state
166+
}}>
167+
{trans("userAuth.register")}
168+
</StyledRouteLink>
169+
</AuthBottomView>
170+
</>
171+
)
172+
}
173+
174+
if (currentStep === CurrentStepEnum.WORKSPACES) {
175+
return (
176+
<>
177+
<AccountLoginWrapper>
178+
<StepBackButton onClick={() => setCurrentStep(CurrentStepEnum.EMAIL)} />
179+
<StepHeader title={"Select your workspace"} />
180+
{orgList.map(org => (
181+
<StyledCard
182+
key={org.orgId}
183+
$selected={organizationId === org.orgId}
184+
onClick={() => {
185+
setOrganizationId(org.orgId);
186+
setCurrentStep(CurrentStepEnum.AUTH_PROVIDERS);
187+
}}
188+
>
189+
{org.orgName}
190+
</StyledCard>
191+
))}
192+
</AccountLoginWrapper>
193+
</>
194+
)
195+
}
196+
197+
return (
198+
<>
199+
<AccountLoginWrapper>
200+
<StepBackButton onClick={() => setCurrentStep(CurrentStepEnum.WORKSPACES)} />
201+
<StepHeader title={"Enter your password"} />
202+
<PasswordInput
203+
className="form-input password-input"
204+
passInputConf={{
205+
label: ' ',
206+
}}
207+
onChange={(value) => setPassword(value)}
208+
valueCheck={() => [true, ""]}
209+
/>
210+
<Flex justify="end" style={{margin: '10px 0'}}>
211+
<Link to={{
212+
pathname: AUTH_FORGOT_PASSWORD_URL,
213+
state: location.state
214+
}}
215+
>
216+
{`${trans("userAuth.forgotPassword")}?`}
217+
</Link>
218+
</Flex>
219+
<ConfirmButton loading={loading} disabled={!account || !password} onClick={onSubmit}>
220+
{trans("userAuth.login")}
221+
</ConfirmButton>
222+
{organizationId && (
223+
<ThirdPartyAuth
224+
invitationId={invitationId}
225+
invitedOrganizationId={organizationId}
226+
authGoal="login"
227+
/>
228+
)}
229+
</AccountLoginWrapper>
230+
<Divider/>
231+
<AuthBottomView>
232+
<StyledRouteLink to={{
233+
pathname: AUTH_REGISTER_URL,
234+
state: location.state
235+
}}>
236+
{trans("userAuth.register")}
237+
</StyledRouteLink>
238+
</AuthBottomView>
239+
</>
240+
);
241+
}

client/packages/lowcoder/src/pages/userAuth/login.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import React, { useContext, useMemo } from "react";
99
import { AuthContext, getLoginTitle } from "pages/userAuth/authUtils";
1010
import styled from "styled-components";
1111
import { requiresUnAuth } from "pages/userAuth/authHOC";
12+
import FormLoginSteps from "./formLoginSteps";
1213

1314
const ThirdAuthWrapper = styled.div`
1415
display: flex;
@@ -87,7 +88,7 @@ function Login() {
8788
const invitationId = inviteInfo?.invitationId;
8889
const location = useLocation();
8990
const queryParams = new URLSearchParams(location.search);
90-
const orgId = useParams<any>().orgId;
91+
const { orgId } = useParams<{orgId?: string}>();
9192

9293
const loginType = systemConfig?.authConfigs.find(
9394
(config) => config.sourceType === queryParams.get(AuthSearchParams.loginType)
@@ -143,7 +144,10 @@ function Login() {
143144
heading={loginHeading}
144145
subHeading={loginSubHeading}
145146
>
146-
<FormLogin organizationId={organizationId} />
147+
{ Boolean(organizationId)
148+
? <FormLogin organizationId={organizationId} />
149+
: <FormLoginSteps />
150+
}
147151
</AuthContainer>
148152
</>
149153
);

0 commit comments

Comments
 (0)