mirror of
https://github.com/balzack/databag.git
synced 2025-04-23 18:15:19 +00:00
implementing settings screen
This commit is contained in:
parent
8f4bd44c35
commit
4c7f5315cc
@ -17,15 +17,15 @@ import left from '../images/login.png'
|
||||
import {
|
||||
IconLock,
|
||||
IconUser,
|
||||
IconUsers,
|
||||
IconSettings,
|
||||
IconServer,
|
||||
IconKey,
|
||||
} from '@tabler/icons-react'
|
||||
import { modals } from '@mantine/modals';
|
||||
|
||||
export function Access() {
|
||||
const { state, actions } = useAccess()
|
||||
const [alertOpened, { open: alertOpen, close: alertClose }] =
|
||||
useDisclosure(false)
|
||||
const [urlOpened, { open: urlOpen, close: urlClose }] = useDisclosure(false)
|
||||
const [otpOpened, { open: otpOpen, close: otpClose }] = useDisclosure(false)
|
||||
const [disabled, setDisabled] = useState(false)
|
||||
@ -58,7 +58,19 @@ export function Access() {
|
||||
}
|
||||
otpOpen()
|
||||
} else {
|
||||
alertOpen()
|
||||
modals.openConfirmModal({
|
||||
title: state.strings.operationFailed,
|
||||
withCloseButton: true,
|
||||
overlayProps: {
|
||||
backgroundOpacity: 0.55,
|
||||
blur: 3,
|
||||
},
|
||||
children: (
|
||||
<div>{state.strings.tryAgain}</div>
|
||||
),
|
||||
cancelProps: { display: 'none' },
|
||||
confirmProps: { display: 'none' },
|
||||
});
|
||||
}
|
||||
}
|
||||
actions.setLoading(false)
|
||||
@ -228,6 +240,7 @@ export function Access() {
|
||||
value={state.username}
|
||||
leftSectionPointerEvents="none"
|
||||
leftSection={<IconUser />}
|
||||
rightSection={state.taken ? <IconUsers /> : null}
|
||||
placeholder={state.strings.username}
|
||||
onChange={(event) =>
|
||||
actions.setUsername(event.currentTarget.value)
|
||||
@ -331,6 +344,7 @@ export function Access() {
|
||||
opened={urlOpened}
|
||||
onClose={urlClose}
|
||||
withCloseButton={false}
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||
centered
|
||||
>
|
||||
<TextInput
|
||||
@ -346,17 +360,11 @@ export function Access() {
|
||||
onChange={(event) => actions.setNode(event.currentTarget.value)}
|
||||
/>
|
||||
</Modal>
|
||||
<Modal
|
||||
opened={alertOpened}
|
||||
onClose={alertClose}
|
||||
title={state.strings.operationFailed}
|
||||
>
|
||||
{state.strings.tryAgain}
|
||||
</Modal>
|
||||
<Modal
|
||||
opened={otpOpened}
|
||||
onClose={otpClose}
|
||||
withCloseButton={false}
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||
centered
|
||||
>
|
||||
<div className={classes.mfa}>
|
||||
|
@ -18,6 +18,10 @@ export function Identity({ settings, contacts }: { settings: () => void, contact
|
||||
const logout = () => modals.openConfirmModal({
|
||||
title: state.strings.confirmLogout,
|
||||
withCloseButton: false,
|
||||
overlayProps: {
|
||||
backgroundOpacity: 0.55,
|
||||
blur: 3,
|
||||
},
|
||||
children: (
|
||||
<Switch label={state.strings.allDevices} size="md" onChange={(ev) => actions.setAll(ev.currentTarget.checked)} />
|
||||
),
|
||||
|
@ -26,7 +26,7 @@ export function Session() {
|
||||
<>
|
||||
<div className={classes.screen}>
|
||||
{ tab === 'settings' && (
|
||||
<Settings />
|
||||
<Settings showLogout={true} />
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.tabs}>
|
||||
@ -61,7 +61,7 @@ export function Session() {
|
||||
<div className={classes.right}>
|
||||
</div>
|
||||
<Drawer opened={settings} onClose={closeSettings} withCloseButton={false} size="sm" position="right">
|
||||
<Settings />
|
||||
<Settings showLogout={false} />
|
||||
</Drawer>
|
||||
</div>
|
||||
)}
|
||||
|
@ -1,3 +1,16 @@
|
||||
.change {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.control {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
padding-top: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.settings {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -1,16 +1,22 @@
|
||||
import { useSettings } from './useSettings.hook';
|
||||
import { Radio, Group, Select, Switch, Text, Image, Button, UnstyledButton } from '@mantine/core';
|
||||
import { Modal, TextInput, PasswordInput, Radio, Group, Select, Switch, Text, Image, Button, UnstyledButton } from '@mantine/core';
|
||||
import classes from './Settings.module.css';
|
||||
import { IconClock, IconCalendar, IconVideo, IconMicrophone, IconWorld, IconBrightness, IconTicket, IconCloudLock, IconBell, IconEye, IconBook, IconMapPin, IconLogout, IconLogin } from '@tabler/icons-react'
|
||||
import { IconLock, IconUser, IconClock, IconCalendar, IconUsers, IconVideo, IconMicrophone, IconWorld, IconBrightness, IconTicket, IconCloudLock, IconBell, IconEye, IconBook, IconMapPin, IconLogout, IconLogin } from '@tabler/icons-react'
|
||||
import avatar from '../images/avatar.png'
|
||||
import { modals } from '@mantine/modals';
|
||||
import { useDisclosure } from '@mantine/hooks'
|
||||
|
||||
export function Settings() {
|
||||
export function Settings({ showLogout }) {
|
||||
const { state, actions } = useSettings();
|
||||
const [changeOpened, { open: changeOpen, close: changeClose }] = useDisclosure(false)
|
||||
|
||||
const logout = () => modals.openConfirmModal({
|
||||
title: state.strings.confirmLogout,
|
||||
withCloseButton: false,
|
||||
overlayProps: {
|
||||
backgroundOpacity: 0.55,
|
||||
blur: 3,
|
||||
},
|
||||
children: (
|
||||
<Switch label={state.strings.allDevices} size="md" onChange={(ev) => actions.setAll(ev.currentTarget.checked)} />
|
||||
),
|
||||
@ -18,6 +24,31 @@ export function Settings() {
|
||||
onConfirm: actions.logout,
|
||||
});
|
||||
|
||||
const setLogin = async () => {
|
||||
try {
|
||||
throw new Error("NO");
|
||||
await actions.setLogin();
|
||||
changeClose();
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
modals.openConfirmModal({
|
||||
title: state.strings.operationFailed,
|
||||
withCloseButton: true,
|
||||
overlayProps: {
|
||||
backgroundOpacity: 0.55,
|
||||
blur: 3,
|
||||
},
|
||||
children: (
|
||||
<Text>{state.strings.tryAgain}</Text>
|
||||
),
|
||||
cancelProps: { display: 'none' },
|
||||
confirmProps: { display: 'none' },
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ state.profileSet && (
|
||||
@ -93,12 +124,14 @@ export function Settings() {
|
||||
<Switch className={classes.entryControl} />
|
||||
</div>
|
||||
<div className={classes.divider} />
|
||||
<div className={classes.entry}>
|
||||
<div className={classes.entryIcon}>
|
||||
<IconLogout />
|
||||
{ showLogout && (
|
||||
<div className={classes.entry}>
|
||||
<div className={classes.entryIcon}>
|
||||
<IconLogout />
|
||||
</div>
|
||||
<Text className={classes.entryLabel} onClick={logout}>{ state.strings.logout }</Text>
|
||||
</div>
|
||||
<Text className={classes.entryLabel} onClick={logout}>{ state.strings.logout }</Text>
|
||||
</div>
|
||||
)}
|
||||
<div className={classes.entry}>
|
||||
<div className={classes.entryIcon}>
|
||||
<IconTicket />
|
||||
@ -110,7 +143,7 @@ export function Settings() {
|
||||
<div className={classes.entryIcon}>
|
||||
<IconLogin />
|
||||
</div>
|
||||
<Text className={classes.entryLabel}>{ state.strings.changeLogin }</Text>
|
||||
<Text className={classes.entryLabel} onClick={changeOpen}>{ state.strings.changeLogin }</Text>
|
||||
</div>
|
||||
<div className={classes.divider} />
|
||||
|
||||
@ -205,6 +238,62 @@ export function Settings() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Modal
|
||||
title={state.strings.changeLogin}
|
||||
opened={changeOpened}
|
||||
onClose={changeClose}
|
||||
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
|
||||
centered
|
||||
>
|
||||
<div className={classes.change}>
|
||||
<TextInput
|
||||
className={classes.input}
|
||||
size="md"
|
||||
value={state.username}
|
||||
leftSectionPointerEvents="none"
|
||||
leftSection={<IconUser />}
|
||||
rightSection={state.taken ? <IconUsers /> : null}
|
||||
placeholder={state.strings.username}
|
||||
onChange={(event) =>
|
||||
actions.setUsername(event.currentTarget.value)
|
||||
}
|
||||
error={state.taken}
|
||||
/>
|
||||
<PasswordInput
|
||||
className={classes.input}
|
||||
size="md"
|
||||
value={state.password}
|
||||
leftSection={<IconLock />}
|
||||
placeholder={state.strings.password}
|
||||
onChange={(event) =>
|
||||
actions.setPassword(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<PasswordInput
|
||||
className={classes.input}
|
||||
size="md"
|
||||
value={state.confirm}
|
||||
leftSection={<IconLock />}
|
||||
placeholder={state.strings.confirmPassword}
|
||||
onChange={(event) =>
|
||||
actions.setConfirm(event.currentTarget.value)
|
||||
}
|
||||
/>
|
||||
<div className={classes.control}>
|
||||
<Button variant="default" onClick={changeClose}>
|
||||
{state.strings.cancel}
|
||||
</Button>
|
||||
<Button
|
||||
variant="filled"
|
||||
onClick={setLogin}
|
||||
loading={state.loading}
|
||||
disabled={state.taken || !state.checked || !state.username || !state.password || state.confirm !== state.password}
|
||||
>
|
||||
{state.strings.save}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
import { useEffect, useState, useContext } from 'react'
|
||||
import { useEffect, useState, useContext, useRef } from 'react'
|
||||
import { AppContext } from '../context/AppContext';
|
||||
import { DisplayContext } from '../context/DisplayContext';
|
||||
import { ContextType } from '../context/ContextType';
|
||||
import { Session, Settings, Identity, type Profile, type Config } from 'databag-client-sdk'
|
||||
|
||||
const DEBOUNCE_MS = 1000;
|
||||
|
||||
export function useSettings() {
|
||||
|
||||
const display = useContext(DisplayContext) as ContextType;
|
||||
const app = useContext(AppContext) as ContextType;
|
||||
const debounce = useRef(useRef(setTimeout(() => {}, 0)));
|
||||
|
||||
const [state, setState] = useState({
|
||||
config: {} as Config,
|
||||
@ -26,6 +29,11 @@ export function useSettings() {
|
||||
timeFormat: '12h',
|
||||
dateFormat: 'mm/dd',
|
||||
all: false,
|
||||
password: '',
|
||||
confirm: '',
|
||||
username: '',
|
||||
taken: false,
|
||||
checked: true,
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -50,7 +58,7 @@ export function useSettings() {
|
||||
}
|
||||
settings.addConfigListener(setConfig);
|
||||
const setProfile = (profile: Profile) => {
|
||||
updateState({ profile, profileSet: true, imageUrl: identity.getProfileImageUrl() })
|
||||
updateState({ profile, username: profile.handle, profileSet: true, imageUrl: identity.getProfileImageUrl() })
|
||||
}
|
||||
identity.addProfileListener(setProfile)
|
||||
return () => {
|
||||
@ -168,6 +176,30 @@ console.log(audioInputs, videoInputs);
|
||||
logout: async () => {
|
||||
await app.actions.accountLogout(state.all);
|
||||
},
|
||||
setUsername: (username) => {
|
||||
updateState({ username, taken: false, checked: false });
|
||||
clearTimeout(debounce.current);
|
||||
if (!username || username === state.profile.handle) {
|
||||
updateState({ available: true, checked: true});
|
||||
}
|
||||
else {
|
||||
debounce.current = setTimeout(async () => {
|
||||
const { settings } = getSession();
|
||||
const available = await settings.getUsernameStatus(username);
|
||||
updateState({ taken: !available, checked: true });
|
||||
}, DEBOUNCE_MS);
|
||||
}
|
||||
},
|
||||
setPassword: (password) => {
|
||||
updateState({ password });
|
||||
},
|
||||
setConfirm: (confirm) => {
|
||||
updateState({ confirm });
|
||||
},
|
||||
setLogin: async () => {
|
||||
const { settings } = getSession();
|
||||
await settings.setLogin(state.username, state.password);
|
||||
}
|
||||
}
|
||||
|
||||
return { state, actions }
|
||||
|
@ -51,7 +51,7 @@ export class DatabagSDK {
|
||||
}
|
||||
|
||||
public async username(name: string, token: string, node: string, secure: boolean): Promise<boolean> {
|
||||
return await getUsername(name, token, node, secure);
|
||||
return await getUsername(name, token, null, node, secure);
|
||||
}
|
||||
|
||||
public async login(handle: string, password: string, node: string, secure: boolean, mfaCode: string | null, params: SessionParams): Promise<Session> {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import axios from 'redaxios';
|
||||
|
||||
export async function getUsername(name: string, token: string, node: string, secure: boolean): Promise<boolean> {
|
||||
const param = token ? `&token=${token}` : '';
|
||||
export async function getUsername(name: string, token: string | null, agent: string | null, node: string, secure: boolean): Promise<boolean> {
|
||||
const param = token ? `&token=${token}` : agent ? `&agent=${agent}` : '';
|
||||
const username = encodeURIComponent(name)
|
||||
const endpoint = `http${secure ? 's' : ''}://${node}/account/username?name=${username}${param}`;
|
||||
const response = await axios.get(endpoint);
|
||||
|
@ -211,7 +211,7 @@ export class SettingsModule implements Settings {
|
||||
|
||||
public async getUsernameStatus(username: string): Promise<boolean> {
|
||||
const { node, secure, token } = this;
|
||||
return await getUsername(username, token, node, secure);
|
||||
return await getUsername(username, null, token, node, secure);
|
||||
}
|
||||
|
||||
public async setLogin(username: string, password: string): Promise<void> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user