mirror of
https://github.com/balzack/databag.git
synced 2025-02-14 12:39:17 +00:00
supporting login change as resuable component
This commit is contained in:
parent
4b6b981d3d
commit
37978e65b7
@ -2,6 +2,7 @@ import { AccountWrapper } from './Account.styled';
|
||||
import { DoubleRightOutlined } from '@ant-design/icons';
|
||||
import { Checkbox } from 'antd';
|
||||
import { SettingOutlined, LockOutlined } from '@ant-design/icons';
|
||||
import { AccountAccess } from '../accountAccess/AccountAccess';
|
||||
|
||||
export function Account({ closeAccount, openProfile }) {
|
||||
|
||||
@ -14,15 +15,11 @@ export function Account({ closeAccount, openProfile }) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<Checkbox>Visible in Registry</Checkbox>
|
||||
<div class="link" onClick={openProfile}>
|
||||
<SettingOutlined />
|
||||
<div class="label">Update Profile</div>
|
||||
</div>
|
||||
<div class="link">
|
||||
<LockOutlined />
|
||||
<div class="label">Change Login</div>
|
||||
</div>
|
||||
<AccountAccess />
|
||||
</div>
|
||||
</AccountWrapper>
|
||||
);
|
||||
|
@ -45,6 +45,7 @@ export const AccountWrapper = styled.div`
|
||||
.link {
|
||||
color: ${Colors.primary};
|
||||
padding-top: 16px;
|
||||
padding-bottom: 8px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
60
net/web/src/session/accountAccess/AccountAccess.jsx
Normal file
60
net/web/src/session/accountAccess/AccountAccess.jsx
Normal file
@ -0,0 +1,60 @@
|
||||
import { AccountAccessWrapper, EditFooter } from './AccountAccess.styled';
|
||||
import { useAccountAccess } from './useAccountAccess.hook';
|
||||
import { AccountLogin } from './accountLogin/AccountLogin';
|
||||
import { Button, Modal, Checkbox } from 'antd';
|
||||
import { LockOutlined } from '@ant-design/icons';
|
||||
|
||||
export function AccountAccess() {
|
||||
|
||||
const { state, actions } = useAccountAccess();
|
||||
|
||||
const saveSearchable = async (e) => {
|
||||
try {
|
||||
await actions.setSearchable(e.target.checked);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
Modal.error({
|
||||
title: 'Update Registry Failed',
|
||||
content: 'Please try again.',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const saveLogin = async () => {
|
||||
try {
|
||||
await actions.setLogin();
|
||||
actions.clearEditLogin();
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
Modal.error({
|
||||
title: 'Failed to Save',
|
||||
comment: 'Please try again.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const editLoginFooter = (
|
||||
<EditFooter>
|
||||
<div class="select"></div>
|
||||
<Button key="back" onClick={actions.clearEditLogin}>Cancel</Button>
|
||||
<Button key="save" type="primary" onClick={saveLogin} disabled={!actions.canSaveLogin()} loading={state.busy}>Save</Button>
|
||||
</EditFooter>
|
||||
);
|
||||
|
||||
return (
|
||||
<AccountAccessWrapper>
|
||||
<Checkbox checked={state.searchable} onChange={(e) => saveSearchable(e)}>Visible in Registry</Checkbox>
|
||||
<div class="link" onClick={actions.setEditLogin}>
|
||||
<LockOutlined />
|
||||
<div class="label">Change Login</div>
|
||||
</div>
|
||||
<Modal title="Account Login" centered visible={state.editLogin} footer={editLoginFooter}
|
||||
onCancel={actions.clearEditLogin}>
|
||||
<AccountLogin state={state} actions={actions} />
|
||||
</Modal>
|
||||
</AccountAccessWrapper>
|
||||
);
|
||||
}
|
||||
|
33
net/web/src/session/accountAccess/AccountAccess.styled.js
Normal file
33
net/web/src/session/accountAccess/AccountAccess.styled.js
Normal file
@ -0,0 +1,33 @@
|
||||
import styled from 'styled-components';
|
||||
import Colors from 'constants/Colors';
|
||||
|
||||
export const AccountAccessWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: ${Colors.primary};
|
||||
padding-top: 8px;
|
||||
|
||||
.label {
|
||||
padding-left: 8px;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const EditFooter = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.select {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
`
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { Form, Input, Space } from 'antd';
|
||||
import { LockOutlined, UserOutlined } from '@ant-design/icons';
|
||||
|
||||
export function AccountLogin({ state, actions }) {
|
||||
return (
|
||||
<Form name="basic" wrapperCol={{ span: 24, }}>
|
||||
<Form.Item name="username" validateStatus={state.editStatus} help={state.editMessage}>
|
||||
<Input placeholder="Username" spellCheck="false" onChange={(e) => actions.setEditHandle(e.target.value)}
|
||||
defaultValue={state.editHandle} autocomplete="username" autocapitalize="none" prefix={<UserOutlined />} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="password">
|
||||
<Input.Password placeholder="Password" spellCheck="false" onChange={(e) => actions.setEditPassword(e.target.value)}
|
||||
autocomplete="new-password" prefix={<LockOutlined />} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item name="confirm">
|
||||
<Input.Password placeholder="Confirm Password" spellCheck="false" onChange={(e) => actions.setEditConfirm(e.target.value)}
|
||||
autocomplete="new-password" prefix={<LockOutlined />} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
124
net/web/src/session/accountAccess/useAccountAccess.hook.js
Normal file
124
net/web/src/session/accountAccess/useAccountAccess.hook.js
Normal file
@ -0,0 +1,124 @@
|
||||
import { useRef, useState, useEffect, useContext } from 'react';
|
||||
import { AccountContext } from 'context/AccountContext';
|
||||
import { ProfileContext } from 'context/ProfileContext';
|
||||
import { getUsername } from 'api/getUsername';
|
||||
|
||||
export function useAccountAccess() {
|
||||
|
||||
const [state, setState] = useState({
|
||||
editLogin: false,
|
||||
handle: null,
|
||||
editHandle: null,
|
||||
editStatus: null,
|
||||
editMessage: null,
|
||||
editPassword: null,
|
||||
editConfirm: null,
|
||||
busy: false,
|
||||
searchable: null,
|
||||
checked: true,
|
||||
});
|
||||
|
||||
const profile = useContext(ProfileContext);
|
||||
const account = useContext(AccountContext);
|
||||
const debounce = useRef(null);
|
||||
|
||||
const updateState = (value) => {
|
||||
setState((s) => ({ ...s, ...value }));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (profile.state.init) {
|
||||
const { handle } = profile.state.profile;
|
||||
updateState({ handle, editHandle: handle });
|
||||
}
|
||||
}, [profile]);
|
||||
|
||||
useEffect(() => {
|
||||
if (account?.state?.status) {
|
||||
updateState({ searchable: account.state.status.searchable });
|
||||
}
|
||||
}, [account]);
|
||||
|
||||
const actions = {
|
||||
setEditLogin: () => {
|
||||
updateState({ editLogin: true });
|
||||
},
|
||||
clearEditLogin: () => {
|
||||
updateState({ editLogin: false });
|
||||
},
|
||||
setEditHandle: (editHandle) => {
|
||||
updateState({ checked: false, editHandle });
|
||||
clearTimeout(debounce.current);
|
||||
debounce.current = setTimeout(async () => {
|
||||
if (editHandle.toLowerCase() === state.handle.toLowerCase()) {
|
||||
updateState({ checked: true, editStatus: 'success', editMessage: '' });
|
||||
}
|
||||
try {
|
||||
let valid = await getUsername(editHandle);
|
||||
if (valid) {
|
||||
updateState({ checked: true, editStatus: 'success', editMessage: '' });
|
||||
}
|
||||
else {
|
||||
updateState({ checked: true, editStatus: 'error', editMessage: 'Username is not available' });
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
updateState({ checked: true, editStatus: 'success', editMessage: '' });
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
setEditPassword: (editPassword) => {
|
||||
updateState({ editPassword });
|
||||
},
|
||||
setEditConfirm: (editConfirm) => {
|
||||
updateState({ editConfirm });
|
||||
},
|
||||
canSaveLogin: () => {
|
||||
if(state.editStatus === 'error' || !state.checked) {
|
||||
return false;
|
||||
}
|
||||
if(state.editHandle && state.editPassword && state.editPassword === state.editConfirm) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
setLogin: async () => {
|
||||
if (!state.editHandle || !state.editPassword || state.editPassword !== state.editConfirm) {
|
||||
throw new Error("Invalid login credentials");
|
||||
}
|
||||
if (!state.busy) {
|
||||
try {
|
||||
updateState({ busy: true });
|
||||
await account.actions.setLogin(state.editHandle, state.editPassword);
|
||||
updateState({ busy: false });
|
||||
}
|
||||
catch(err) {
|
||||
console.log(err);
|
||||
updateState({ busy: false });
|
||||
throw new Error("failed to update login");
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error("save in progress");
|
||||
}
|
||||
},
|
||||
setSearchable: async (flag) => {
|
||||
if (!state.busy) {
|
||||
try {
|
||||
updateState({ busy: true });
|
||||
await account.actions.setSearchable(flag);
|
||||
updateState({ busy: false });
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
throw new Error('failed to set searchable');
|
||||
updateState({ busy: false });
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return { state, actions };
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { useRef } from 'react';
|
||||
import { Modal, Button, Checkbox } from 'antd';
|
||||
import { Modal, Button } from 'antd';
|
||||
import { ProfileWrapper, EditFooter } from './Profile.styled';
|
||||
import { useProfile } from './useProfile.hook';
|
||||
import { ProfileImage } from './profileImage/ProfileImage';
|
||||
import { ProfileDetails } from './profileDetails/ProfileDetails';
|
||||
import { Logo } from 'logo/Logo';
|
||||
import { AccountAccess } from '../accountAccess/AccountAccess';
|
||||
import { LogoutOutlined, DatabaseOutlined, LockOutlined, RightOutlined, EditOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons';
|
||||
|
||||
export function Profile({ closeProfile }) {
|
||||
@ -48,19 +49,6 @@ export function Profile({ closeProfile }) {
|
||||
}
|
||||
}
|
||||
|
||||
const saveSearchable = async (e) => {
|
||||
try {
|
||||
await actions.setSearchable(e.target.checked);
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
Modal.error({
|
||||
title: 'Update Registry Failed',
|
||||
content: 'Please try again.',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
Modal.confirm({
|
||||
title: 'Are you sure you want to logout?',
|
||||
@ -146,11 +134,7 @@ export function Profile({ closeProfile }) {
|
||||
</div>
|
||||
<div class="section">Account Settings</div>
|
||||
<div class="controls">
|
||||
<Checkbox checked={state.searchable} onChange={(e) => saveSearchable(e)}>Visible in Registry</Checkbox>
|
||||
<div class="link">
|
||||
<LockOutlined />
|
||||
<div class="label">Change Login</div>
|
||||
</div>
|
||||
<AccountAccess />
|
||||
{ state.display === 'small' && (
|
||||
<div class="logout" onClick={logout}>
|
||||
<LogoutOutlined />
|
||||
|
@ -6,15 +6,15 @@ export function ProfileDetails({ state, actions }) {
|
||||
<ProfileDetailsWrapper>
|
||||
<div class="info">
|
||||
<Input placeholder="Name" spellCheck="false" onChange={(e) => actions.setEditName(e.target.value)}
|
||||
defaultValue={state.name} autocapitalizate="word" />
|
||||
defaultValue={state.editName} autocapitalizate="word" />
|
||||
</div>
|
||||
<div class="info">
|
||||
<Input placeholder="Location" spellCheck="false" onChange={(e) => actions.setEditLocation(e.target.value)}
|
||||
defaultValue={state.location} autocapitalizate="word" />
|
||||
defaultValue={state.editLocation} autocapitalizate="word" />
|
||||
</div>
|
||||
<div class="info">
|
||||
<Input.TextArea placeholder="Description" onChange={(e) => actions.setEditDescription(e.target.value)}
|
||||
spellCheck="false" defaultValue={state.description} autoSize={{ minRows: 2, maxRows: 6 }} />
|
||||
spellCheck="false" defaultValue={state.editDescription} autoSize={{ minRows: 2, maxRows: 6 }} />
|
||||
</div>
|
||||
</ProfileDetailsWrapper>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useState, useEffect, useContext } from 'react';
|
||||
import { useRef, useState, useEffect, useContext } from 'react';
|
||||
import { ProfileContext } from 'context/ProfileContext';
|
||||
import { AccountContext } from 'context/AccountContext';
|
||||
import { AppContext } from 'context/AppContext';
|
||||
@ -10,6 +10,8 @@ export function useProfile() {
|
||||
const [state, setState] = useState({
|
||||
init: false,
|
||||
editProfileImage: false,
|
||||
editProfileDetails: false,
|
||||
handle: null,
|
||||
name: null,
|
||||
location: null,
|
||||
description: null,
|
||||
@ -20,6 +22,7 @@ export function useProfile() {
|
||||
crop: { w: 0, h: 0, x: 0, y: 0 },
|
||||
busy: false,
|
||||
searchable: null,
|
||||
checked: true,
|
||||
});
|
||||
|
||||
const IMAGE_DIM = 256;
|
||||
@ -37,7 +40,8 @@ export function useProfile() {
|
||||
const { node, name, handle, location, description, image } = profile.state.profile;
|
||||
let url = !image ? null : profile.actions.profileImageUrl();
|
||||
let editImage = !image ? avatar : url;
|
||||
updateState({ init: true, name, node, handle, url, editImage, location, description });
|
||||
updateState({ init: true, name, location, description, node, handle, url,
|
||||
editName: name, editLocation: location, editDescription: description, editHandle: handle, editImage });
|
||||
}
|
||||
}, [profile]);
|
||||
|
||||
@ -45,12 +49,6 @@ export function useProfile() {
|
||||
updateState({ display: viewport.state.display });
|
||||
}, [viewport]);
|
||||
|
||||
useEffect(() => {
|
||||
if (account?.state?.status) {
|
||||
updateState({ searchable: account.state.status.searchable });
|
||||
}
|
||||
}, [account]);
|
||||
|
||||
const actions = {
|
||||
logout: app.actions.logout,
|
||||
setEditImage: (value) => {
|
||||
@ -133,20 +131,6 @@ export function useProfile() {
|
||||
throw new Error('save in progress');
|
||||
}
|
||||
},
|
||||
setSearchable: async (flag) => {
|
||||
if (!state.busy) {
|
||||
try {
|
||||
updateState({ busy: true });
|
||||
await account.actions.setSearchable(flag);
|
||||
updateState({ busy: false });
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
throw new Error('failed to set searchable');
|
||||
updateState({ busy: false });
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return { state, actions };
|
||||
|
Loading…
Reference in New Issue
Block a user