adding profile detail modal

This commit is contained in:
balzack 2024-09-08 08:25:04 -07:00
parent 4c7f5315cc
commit 214782d9c9
3 changed files with 181 additions and 72 deletions

View File

@ -16,7 +16,7 @@
flex-direction: column;
align-items: center;
padding: 8px;
gap: 8px;
gap: 2px;
.header {
font-size: 22px;
@ -115,20 +115,19 @@
width: 100%;
padding-left: 8px;
display: flex;
align-items: center;
.radio {
position: relative;
top: -4px;
top: -8px;
}
.entryIcon {
width: 32px;
display: flex;
}
.entrySet {
font-size: 14px;
white-space: preserve;
}
.entryUnset {
@ -166,6 +165,8 @@
align-items: center;
max-width: 40%;
padding: 0;
position: relative;
top: -4px;
}
}
}

View File

@ -1,14 +1,18 @@
import { useSettings } from './useSettings.hook';
import { Modal, TextInput, PasswordInput, Radio, Group, Select, Switch, Text, Image, Button, UnstyledButton } from '@mantine/core';
import { Modal, Textarea, TextInput, PasswordInput, Radio, Group, Select, Switch, Text, Image, Button, UnstyledButton } from '@mantine/core';
import classes from './Settings.module.css';
import { IconLock, IconUser, IconClock, IconCalendar, IconUsers, IconVideo, IconMicrophone, IconWorld, IconBrightness, IconTicket, IconCloudLock, IconBell, IconEye, IconBook, IconMapPin, IconLogout, IconLogin } from '@tabler/icons-react'
import { IconLock, IconUser, IconClock, IconIdBadge, 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'
import { useState } from 'react'
export function Settings({ showLogout }) {
const { state, actions } = useSettings();
const [changeOpened, { open: changeOpen, close: changeClose }] = useDisclosure(false)
const [detailsOpened, { open: detailsOpen, close: detailsClose }] = useDisclosure(false)
const [savingLogin, setSavingLogin] = useState(false);
const [savingDetails, setSavingDetails] = useState(false);
const logout = () => modals.openConfirmModal({
title: state.strings.confirmLogout,
@ -24,28 +28,57 @@ export function Settings({ showLogout }) {
onConfirm: actions.logout,
});
const setLogin = async () => {
try {
throw new Error("NO");
await actions.setLogin();
changeClose();
const setDetails = async () => {
if (!savingDetails) {
setSavingDetails(true);
try {
await actions.setDetails();
detailsClose();
}
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' },
});
}
setSavingDetails(false);
}
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' },
});
}
const setLogin = async () => {
if (!savingLogin) {
setSavingLogin(true);
try {
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' },
});
}
setSavingLogin(false);
}
}
@ -72,7 +105,7 @@ throw new Error("NO");
</div>
<div className={classes.section}>
<div className={classes.divider} />
<UnstyledButton className={classes.sectionEdit}>{ state.strings.edit }</UnstyledButton>
<UnstyledButton className={classes.sectionEdit} onClick={detailsOpen}>{ state.strings.edit }</UnstyledButton>
</div>
{ !state.profile.name && (
<Text className={classes.nameUnset}>{state.strings.name}</Text>
@ -146,43 +179,41 @@ throw new Error("NO");
<Text className={classes.entryLabel} onClick={changeOpen}>{ state.strings.changeLogin }</Text>
</div>
<div className={classes.divider} />
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconClock />
</div>
<Text className={classes.controlLabel}>{ state.strings.timeFormat }</Text>
<Radio.Group
name="timeFormat"
className={classes.radio}
value={state.timeFormat}
onChange={actions.setTimeFormat}
>
<Group mt="xs">
<Radio value="12h" label={ state.strings.timeUs } />
<Radio value="24h" label={ state.strings.timeEu } />
</Group>
</Radio.Group>
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconCalendar />
</div>
<Text className={classes.controlLabel}>{ state.strings.dateFormat }</Text>
<Radio.Group
name="dateFormat"
className={classes.radio}
value={state.dateFormat}
onChange={actions.setDateFormat}
>
<Group mt="xs">
<Radio value="mm/dd" label={ state.strings.dateUs } />
<Radio value="dd/mm" label={ state.strings.dateEu } />
</Group>
</Radio.Group>
</div>
<div className={classes.selects}>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconClock />
</div>
<Text className={classes.controlLabel}>{ state.strings.timeFormat }</Text>
<Radio.Group
name="timeFormat"
className={classes.radio}
value={state.timeFormat}
onChange={actions.setTimeFormat}
>
<Group mt="xs">
<Radio value="12h" label={ state.strings.timeUs } />
<Radio value="24h" label={ state.strings.timeEu } />
</Group>
</Radio.Group>
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconCalendar />
</div>
<Text className={classes.controlLabel}>{ state.strings.dateFormat }</Text>
<Radio.Group
name="dateFormat"
className={classes.radio}
value={state.dateFormat}
onChange={actions.setDateFormat}
>
<Group mt="xs">
<Radio value="mm/dd" label={ state.strings.dateUs } />
<Radio value="dd/mm" label={ state.strings.dateEu } />
</Group>
</Radio.Group>
</div>
<div className={classes.entry}>
<div className={classes.entryIcon}>
<IconBrightness />
@ -249,13 +280,13 @@ throw new Error("NO");
<TextInput
className={classes.input}
size="md"
value={state.username}
value={state.handle}
leftSectionPointerEvents="none"
leftSection={<IconUser />}
rightSection={state.taken ? <IconUsers /> : null}
placeholder={state.strings.username}
onChange={(event) =>
actions.setUsername(event.currentTarget.value)
actions.setHandle(event.currentTarget.value)
}
error={state.taken}
/>
@ -286,8 +317,66 @@ throw new Error("NO");
<Button
variant="filled"
onClick={setLogin}
loading={state.loading}
disabled={state.taken || !state.checked || !state.username || !state.password || state.confirm !== state.password}
loading={savingLogin}
disabled={state.taken || !state.checked || !state.handle || !state.password || state.confirm !== state.password}
>
{state.strings.save}
</Button>
</div>
</div>
</Modal>
<Modal
title={state.strings.profileDetails}
opened={detailsOpened}
onClose={detailsClose}
overlayProps={{ backgroundOpacity: 0.55, blur: 3 }}
centered
>
<div className={classes.change}>
<TextInput
className={classes.input}
size="md"
value={state.name}
leftSectionPointerEvents="none"
leftSection={<IconIdBadge />}
placeholder={state.strings.name}
onChange={(event) =>
actions.setName(event.currentTarget.value)
}
/>
<TextInput
className={classes.input}
size="md"
value={state.location}
leftSectionPointerEvents="none"
leftSection={<IconMapPin />}
placeholder={state.strings.location}
onChange={(event) =>
actions.setLocation(event.currentTarget.value)
}
/>
<Textarea
className={classes.input}
size="md"
minRows={1}
maxRows={4}
autosize={true}
value={state.description}
leftSectionPointerEvents="none"
leftSection={<IconBook />}
placeholder={state.strings.description}
onChange={(event) =>
actions.setDescription(event.currentTarget.value)
}
/>
<div className={classes.control}>
<Button variant="default" onClick={detailsClose}>
{state.strings.cancel}
</Button>
<Button
variant="filled"
onClick={setDetails}
loading={savingDetails}
>
{state.strings.save}
</Button>

View File

@ -34,6 +34,10 @@ export function useSettings() {
username: '',
taken: false,
checked: true,
name: '',
description: '',
location: '',
handle: '',
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -58,7 +62,8 @@ export function useSettings() {
}
settings.addConfigListener(setConfig);
const setProfile = (profile: Profile) => {
updateState({ profile, username: profile.handle, profileSet: true, imageUrl: identity.getProfileImageUrl() })
const { handle, name, location, description } = profile;
updateState({ profile, handle, name, location, description, profileSet: true, imageUrl: identity.getProfileImageUrl() })
}
identity.addProfileListener(setProfile)
return () => {
@ -176,16 +181,16 @@ console.log(audioInputs, videoInputs);
logout: async () => {
await app.actions.accountLogout(state.all);
},
setUsername: (username) => {
updateState({ username, taken: false, checked: false });
setHandle: (handle) => {
updateState({ handle, taken: false, checked: false });
clearTimeout(debounce.current);
if (!username || username === state.profile.handle) {
if (!handle || handle === state.profile.handle) {
updateState({ available: true, checked: true});
}
else {
debounce.current = setTimeout(async () => {
const { settings } = getSession();
const available = await settings.getUsernameStatus(username);
const available = await settings.getUsernameStatus(handle);
updateState({ taken: !available, checked: true });
}, DEBOUNCE_MS);
}
@ -199,6 +204,20 @@ console.log(audioInputs, videoInputs);
setLogin: async () => {
const { settings } = getSession();
await settings.setLogin(state.username, state.password);
},
setName: (name) => {
updateState({ name });
},
setLocation: (location) => {
updateState({ location });
},
setDescription: (description) => {
updateState({ description });
},
setDetails: async () => {
const { identity } = getSession();
const { name, location, description } = state;
await identity.setProfileData(name, location, description);
}
}