mirror of
https://github.com/balzack/databag.git
synced 2025-02-15 04:59:16 +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 { DoubleRightOutlined } from '@ant-design/icons';
|
||||||
import { Checkbox } from 'antd';
|
import { Checkbox } from 'antd';
|
||||||
import { SettingOutlined, LockOutlined } from '@ant-design/icons';
|
import { SettingOutlined, LockOutlined } from '@ant-design/icons';
|
||||||
|
import { AccountAccess } from '../accountAccess/AccountAccess';
|
||||||
|
|
||||||
export function Account({ closeAccount, openProfile }) {
|
export function Account({ closeAccount, openProfile }) {
|
||||||
|
|
||||||
@ -14,15 +15,11 @@ export function Account({ closeAccount, openProfile }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<Checkbox>Visible in Registry</Checkbox>
|
|
||||||
<div class="link" onClick={openProfile}>
|
<div class="link" onClick={openProfile}>
|
||||||
<SettingOutlined />
|
<SettingOutlined />
|
||||||
<div class="label">Update Profile</div>
|
<div class="label">Update Profile</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="link">
|
<AccountAccess />
|
||||||
<LockOutlined />
|
|
||||||
<div class="label">Change Login</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</AccountWrapper>
|
</AccountWrapper>
|
||||||
);
|
);
|
||||||
|
@ -45,6 +45,7 @@ export const AccountWrapper = styled.div`
|
|||||||
.link {
|
.link {
|
||||||
color: ${Colors.primary};
|
color: ${Colors.primary};
|
||||||
padding-top: 16px;
|
padding-top: 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
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 { useRef } from 'react';
|
||||||
import { Modal, Button, Checkbox } from 'antd';
|
import { Modal, Button } from 'antd';
|
||||||
import { ProfileWrapper, EditFooter } from './Profile.styled';
|
import { ProfileWrapper, EditFooter } from './Profile.styled';
|
||||||
import { useProfile } from './useProfile.hook';
|
import { useProfile } from './useProfile.hook';
|
||||||
import { ProfileImage } from './profileImage/ProfileImage';
|
import { ProfileImage } from './profileImage/ProfileImage';
|
||||||
import { ProfileDetails } from './profileDetails/ProfileDetails';
|
import { ProfileDetails } from './profileDetails/ProfileDetails';
|
||||||
import { Logo } from 'logo/Logo';
|
import { Logo } from 'logo/Logo';
|
||||||
|
import { AccountAccess } from '../accountAccess/AccountAccess';
|
||||||
import { LogoutOutlined, DatabaseOutlined, LockOutlined, RightOutlined, EditOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons';
|
import { LogoutOutlined, DatabaseOutlined, LockOutlined, RightOutlined, EditOutlined, BookOutlined, EnvironmentOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
export function Profile({ closeProfile }) {
|
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 = () => {
|
const logout = () => {
|
||||||
Modal.confirm({
|
Modal.confirm({
|
||||||
title: 'Are you sure you want to logout?',
|
title: 'Are you sure you want to logout?',
|
||||||
@ -146,11 +134,7 @@ export function Profile({ closeProfile }) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="section">Account Settings</div>
|
<div class="section">Account Settings</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<Checkbox checked={state.searchable} onChange={(e) => saveSearchable(e)}>Visible in Registry</Checkbox>
|
<AccountAccess />
|
||||||
<div class="link">
|
|
||||||
<LockOutlined />
|
|
||||||
<div class="label">Change Login</div>
|
|
||||||
</div>
|
|
||||||
{ state.display === 'small' && (
|
{ state.display === 'small' && (
|
||||||
<div class="logout" onClick={logout}>
|
<div class="logout" onClick={logout}>
|
||||||
<LogoutOutlined />
|
<LogoutOutlined />
|
||||||
|
@ -6,15 +6,15 @@ export function ProfileDetails({ state, actions }) {
|
|||||||
<ProfileDetailsWrapper>
|
<ProfileDetailsWrapper>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<Input placeholder="Name" spellCheck="false" onChange={(e) => actions.setEditName(e.target.value)}
|
<Input placeholder="Name" spellCheck="false" onChange={(e) => actions.setEditName(e.target.value)}
|
||||||
defaultValue={state.name} autocapitalizate="word" />
|
defaultValue={state.editName} autocapitalizate="word" />
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<Input placeholder="Location" spellCheck="false" onChange={(e) => actions.setEditLocation(e.target.value)}
|
<Input placeholder="Location" spellCheck="false" onChange={(e) => actions.setEditLocation(e.target.value)}
|
||||||
defaultValue={state.location} autocapitalizate="word" />
|
defaultValue={state.editLocation} autocapitalizate="word" />
|
||||||
</div>
|
</div>
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<Input.TextArea placeholder="Description" onChange={(e) => actions.setEditDescription(e.target.value)}
|
<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>
|
</div>
|
||||||
</ProfileDetailsWrapper>
|
</ProfileDetailsWrapper>
|
||||||
);
|
);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useEffect, useContext } from 'react';
|
import { useRef, useState, useEffect, useContext } from 'react';
|
||||||
import { ProfileContext } from 'context/ProfileContext';
|
import { ProfileContext } from 'context/ProfileContext';
|
||||||
import { AccountContext } from 'context/AccountContext';
|
import { AccountContext } from 'context/AccountContext';
|
||||||
import { AppContext } from 'context/AppContext';
|
import { AppContext } from 'context/AppContext';
|
||||||
@ -10,6 +10,8 @@ export function useProfile() {
|
|||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
init: false,
|
init: false,
|
||||||
editProfileImage: false,
|
editProfileImage: false,
|
||||||
|
editProfileDetails: false,
|
||||||
|
handle: null,
|
||||||
name: null,
|
name: null,
|
||||||
location: null,
|
location: null,
|
||||||
description: null,
|
description: null,
|
||||||
@ -20,6 +22,7 @@ export function useProfile() {
|
|||||||
crop: { w: 0, h: 0, x: 0, y: 0 },
|
crop: { w: 0, h: 0, x: 0, y: 0 },
|
||||||
busy: false,
|
busy: false,
|
||||||
searchable: null,
|
searchable: null,
|
||||||
|
checked: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const IMAGE_DIM = 256;
|
const IMAGE_DIM = 256;
|
||||||
@ -37,7 +40,8 @@ export function useProfile() {
|
|||||||
const { node, name, handle, location, description, image } = profile.state.profile;
|
const { node, name, handle, location, description, image } = profile.state.profile;
|
||||||
let url = !image ? null : profile.actions.profileImageUrl();
|
let url = !image ? null : profile.actions.profileImageUrl();
|
||||||
let editImage = !image ? avatar : url;
|
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]);
|
}, [profile]);
|
||||||
|
|
||||||
@ -45,12 +49,6 @@ export function useProfile() {
|
|||||||
updateState({ display: viewport.state.display });
|
updateState({ display: viewport.state.display });
|
||||||
}, [viewport]);
|
}, [viewport]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (account?.state?.status) {
|
|
||||||
updateState({ searchable: account.state.status.searchable });
|
|
||||||
}
|
|
||||||
}, [account]);
|
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
logout: app.actions.logout,
|
logout: app.actions.logout,
|
||||||
setEditImage: (value) => {
|
setEditImage: (value) => {
|
||||||
@ -133,20 +131,6 @@ export function useProfile() {
|
|||||||
throw new Error('save in progress');
|
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 };
|
return { state, actions };
|
||||||
|
Loading…
Reference in New Issue
Block a user