Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(console): support email register #921

Merged
merged 1 commit into from
Aug 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion console/src/api/ApiHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ export default function ApiHeader() {
} else {
redirect = '/'
}
if (!location.pathname.startsWith('/login')) {

const shouldRedirect =
['/login', '/signup', '/create-account'].filter((uri) => location.pathname.startsWith(uri))
.length === 0
if (shouldRedirect) {
window.location.href = `${window.location.protocol}//${
window.location.host
}/login?redirect=${encodeURIComponent(redirect)}`
Expand Down
16 changes: 16 additions & 0 deletions console/src/components/Checkbox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react'
import { Checkbox as BaseCheckbox, CheckboxProps } from 'baseui/checkbox'

export interface ICheckBoxProps extends Omit<CheckboxProps, 'value' | 'onChange'> {
value?: boolean
onChange?: (checked: boolean) => void
}

/* eslint-disable react/jsx-props-no-spreading */
export default function Checkbox({ value, onChange, children, ...props }: ICheckBoxProps) {
return (
<BaseCheckbox {...props} checked={value} onChange={() => onChange?.(!value)}>
{children}
</BaseCheckbox>
)
}
24 changes: 18 additions & 6 deletions console/src/components/Form/form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,17 +188,29 @@ export function createForm<S extends {} = Store>({

const error = errors && errors[0]

const renderLabel = () => {
if (!props.required) {
return label
}
if (label) {
return React.createElement(
'div',
{ style: { display: 'flex', alignItems: 'center', gap: 4 } },
[
React.createElement('div', {}, '*'),
React.createElement('div', { style: { flexShrink: 0 } }, label),
]
)
}
return null
}

// eslint-disable-next-line react/no-children-prop
return React.createElement(
FormControl,
{
error: error?.toString(),
label: props.required
? React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 4 } }, [
React.createElement('div', {}, '*'),
React.createElement('div', { style: { flexShrink: 0 } }, label),
])
: label,
label: renderLabel(),
caption,
children: childNode,
},
Expand Down
1 change: 1 addition & 0 deletions console/src/components/IconFont/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const iconTypes = [
'a-sortasc',
'a-sortdesc',
'email',
'warning',
'Facebook',
'Twitter',
'Instagram',
Expand Down
7 changes: 5 additions & 2 deletions console/src/consts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@ export const dateWithZeroTimeFormat = 'YYYY-MM-DD 00:00:00'
export const dateTimeFormat = 'YYYY-MM-DD HH:mm:ss'
export const drawerExpandedWidthOfColumnManage = 520
export const passwordMinLength = 8
export const SignupNeedCreateAccount = 'account_not_created'
export const SignupAccountCreated = 'account_created'
export const SignupStepStranger = 'strange_user'
export const SignupStepEmailNeedVerify = 'email_not_verified'
export const SignupStepNeedCreateAccount = 'account_not_created'
export const SignupStepAccountCreated = 'account_created'
export const CreateAccountPageUri = '/create-account'
56 changes: 56 additions & 0 deletions console/src/domain/user/components/EmailConfirm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useState } from 'react'
import { Modal, ModalHeader, ModalBody } from 'baseui/modal'
import useTranslation from '@/hooks/useTranslation'
import IconFont from '@/components/IconFont'
import Button from '@/components/Button'

export interface IEmailConfirmProps {
show: boolean
email?: string
alreadyVerified: () => Promise<void>
resendEmail: () => Promise<void>
}

export default function EmailConfirm({ show, email, alreadyVerified, resendEmail }: IEmailConfirmProps) {
const [t] = useTranslation()
const [resendLoading, setResendLoading] = useState(false)
const [verifiedLoading, setVerifiedLoading] = useState(false)

return (
<Modal isOpen={show} closeable={false}>
<ModalHeader>
<IconFont type='warning' kind='gray' style={{ marginRight: '10px' }} />
{t('Check Your Email')}
</ModalHeader>
<ModalBody>
<p>{t('Please verify the email send from Starwhale')}</p>
<p>{email}</p>
<div style={{ display: 'flex', justifyContent: 'space-around', marginTop: '30px' }}>
<Button
kind='secondary'
isLoading={resendLoading}
onClick={() => {
setResendLoading(true)
resendEmail().finally(() => {
setResendLoading(false)
})
}}
>
{t('Resend Email')}
</Button>
<Button
isLoading={verifiedLoading}
onClick={() => {
setVerifiedLoading(true)
alreadyVerified().finally(() => {
setVerifiedLoading(false)
})
}}
>
{t('Already Verified')}
</Button>
</div>
</ModalBody>
</Modal>
)
}
9 changes: 8 additions & 1 deletion console/src/domain/user/components/ThirdPartyLoginButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@ export default function ThirdPartyLoginButton({ isLogin, vendorName, vendor, ico
startEnhancer={icon}
kind='secondary'
endEnhancer={<IconFont type='arrow_right' />}
overrides={{ BaseButton: { style: { justifyContent: 'space-between', paddingLeft: '20px' } } }}
overrides={{
BaseButton: {
style: { justifyContent: 'space-between', paddingLeft: '20px' },
// make a button type, prevent triggering click event when we press enter in from
// https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission
props: { type: 'button' },
},
}}
onClick={handleClick}
>
{t(isLogin ? 'Log In With' : 'Sign Up With', [vendorName])}
Expand Down
11 changes: 10 additions & 1 deletion console/src/domain/user/schemas/user.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ export interface ILoginUserSchema {
agreement: boolean
}

export interface ISignupUserSchema extends ILoginUserSchema {
agreement: boolean
callback: string
}

export interface IUpdateUserSchema {
userName: string
isEnabled: boolean
Expand All @@ -46,5 +51,9 @@ export interface INewUserSchema {
}

export interface ICloudLoginRespSchema {
data?: string
token?: string
verification?: string
// step will be one of: strange_user, email_not_verified, account_not_created, account_created
step: string
title?: string
}
42 changes: 39 additions & 3 deletions console/src/domain/user/services/user.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import axios from 'axios'
import { IListQuerySchema, IListSchema } from '@/domain/base/schemas/list'
import { IProjectRoleSchema } from '@project/schemas/project'
import { IUserSchema, ILoginUserSchema, IChangePasswordSchema } from '../schemas/user'
import {
IUserSchema,
ILoginUserSchema,
IChangePasswordSchema,
ICloudLoginRespSchema,
ISignupUserSchema,
} from '../schemas/user'

export async function loginUser(data: ILoginUserSchema): Promise<IUserSchema> {
const bodyFormData = new FormData()
Expand Down Expand Up @@ -95,11 +101,41 @@ export async function changeUserPasswd(user: string, currentUserPwd: string, new
return resp.data
}

export async function createAccount(userName: string, verifier: string): Promise<string> {
export async function createAccount(userName: string, verification: string): Promise<ICloudLoginRespSchema> {
const { data } = await axios({
method: 'post',
url: '/swcloud/api/v1/register/account',
data: { userName, verifier },
data: { userName, verification },
})
return data
}

export async function loginUserWithEmail(data: ILoginUserSchema): Promise<ICloudLoginRespSchema> {
const bodyFormData = new FormData()
bodyFormData.append('email', data.userName)
bodyFormData.append('password', data.userPwd)

const resp = await axios({
method: 'post',
url: '/swcloud/api/v1/login/email',
data: bodyFormData,
headers: { 'Content-Type': 'multipart/form-data' },
})

return resp.data
}

export async function signupWithEmail(data: ISignupUserSchema): Promise<ICloudLoginRespSchema> {
const resp = await axios({
method: 'post',
url: '/swcloud/api/v1/register/email',
data: JSON.stringify({ email: data.userName, password: data.userPwd, callback: data.callback }),
headers: { 'Content-Type': 'application/json' },
})

return resp.data
}

export async function resendEmail(data: ISignupUserSchema): Promise<ICloudLoginRespSchema> {
return signupWithEmail(data)
}
23 changes: 22 additions & 1 deletion console/src/i18n/locales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const locales0 = {
en: '{{0}} Name',
},
'sth required': {
en: '{{0}} is required',
en: '{{0}} required',
},
'Model': {
en: 'Model',
Expand Down Expand Up @@ -538,6 +538,27 @@ const locales0 = {
'agreePolicy': {
en: 'I agree to <1>Terms of Service</1> and <3>Privacy Policy</3>',
},
'Should Check the ToS': {
en: 'Please read and check the ToS',
},
'User Not Registered': {
en: 'User not registered',
},
'Check Your Email': {
en: 'Check your email',
},
'Please verify the email send from Starwhale': {
en: 'Please verify the email send from Starwhale',
},
'Resend Email': {
en: 'Resend Email',
},
'Send Email Success': {
en: 'Send email success',
},
'Already Verified': {
en: 'Already Verified',
},
'alreadyHaveAccount': {
en: 'Already have an account?',
},
Expand Down
8 changes: 4 additions & 4 deletions console/src/pages/Home/CreateAccount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@ import { useHistory } from 'react-router-dom'
export default function CreateAccount() {
const [t] = useTranslation()
const title = useSearchParam('title') ?? ''
const verifier = useSearchParam('verifier') ?? ''
const verification = useSearchParam('verification') ?? ''
const history = useHistory()

const handleSubmit = useCallback(
async (name: string) => {
const data = await createAccount(name, verifier)
setToken(data)
const { token } = await createAccount(name, verification)
setToken(token)
// TODO redirect to the page before register
history.push('/')
},
[verifier, history]
[verification, history]
)

return (
Expand Down
Loading