mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-21 08:40:32 +02:00
added optimistic updates
This commit is contained in:
parent
dea48fdea2
commit
91e7b43a5b
@ -20,7 +20,6 @@ export interface AliasDetailsProps {
|
|||||||
|
|
||||||
export default function AliasDetails({alias: aliasValue}: AliasDetailsProps): ReactElement {
|
export default function AliasDetails({alias: aliasValue}: AliasDetailsProps): ReactElement {
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const {enqueueSnackbar} = useSnackbar()
|
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const {encryptionStatus} = useContext(AuthContext)
|
const {encryptionStatus} = useContext(AuthContext)
|
||||||
const address = atob(params.addressInBase64 as string)
|
const address = atob(params.addressInBase64 as string)
|
||||||
|
@ -11,7 +11,7 @@ import deepEqual from "deep-equal"
|
|||||||
import format from "date-fns/format"
|
import format from "date-fns/format"
|
||||||
import update from "immutability-helper"
|
import update from "immutability-helper"
|
||||||
|
|
||||||
import {useMutation} from "@tanstack/react-query"
|
import {QueryKey, useMutation} from "@tanstack/react-query"
|
||||||
import {
|
import {
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
@ -31,6 +31,7 @@ import {FaviconImage, SimpleOverlayInformation} from "~/components"
|
|||||||
import {Alias, AliasNote, DecryptedAlias} from "~/server-types"
|
import {Alias, AliasNote, DecryptedAlias} from "~/server-types"
|
||||||
import {UpdateAliasData, updateAlias} from "~/apis"
|
import {UpdateAliasData, updateAlias} from "~/apis"
|
||||||
import {useErrorSuccessSnacks} from "~/hooks"
|
import {useErrorSuccessSnacks} from "~/hooks"
|
||||||
|
import {queryClient} from "~/constants/react-query"
|
||||||
import AddWebsiteField from "~/route-widgets/AliasDetailRoute/AddWebsiteField"
|
import AddWebsiteField from "~/route-widgets/AliasDetailRoute/AddWebsiteField"
|
||||||
import AuthContext from "~/AuthContext/AuthContext"
|
import AuthContext from "~/AuthContext/AuthContext"
|
||||||
import FormikAutoLockNavigation from "~/LockNavigationContext/FormikAutoLockNavigation"
|
import FormikAutoLockNavigation from "~/LockNavigationContext/FormikAutoLockNavigation"
|
||||||
@ -40,7 +41,7 @@ export interface AliasNotesFormProps {
|
|||||||
id: string
|
id: string
|
||||||
notes: AliasNote
|
notes: AliasNote
|
||||||
|
|
||||||
onChanged: (alias: DecryptedAlias) => void
|
queryKey: QueryKey
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Form {
|
interface Form {
|
||||||
@ -50,7 +51,7 @@ interface Form {
|
|||||||
detail?: string
|
detail?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AliasNotesForm({id, notes, onChanged}: AliasNotesFormProps): ReactElement {
|
export default function AliasNotesForm({id, notes, queryKey}: AliasNotesFormProps): ReactElement {
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const {showError, showSuccess} = useErrorSuccessSnacks()
|
const {showError, showSuccess} = useErrorSuccessSnacks()
|
||||||
const {_encryptUsingMasterPassword, _decryptUsingMasterPassword} = useContext(AuthContext)
|
const {_encryptUsingMasterPassword, _decryptUsingMasterPassword} = useContext(AuthContext)
|
||||||
@ -72,7 +73,7 @@ export default function AliasNotesForm({id, notes, onChanged}: AliasNotesFormPro
|
|||||||
const {mutateAsync} = useMutation<Alias, AxiosError, UpdateAliasData>(
|
const {mutateAsync} = useMutation<Alias, AxiosError, UpdateAliasData>(
|
||||||
values => updateAlias(id, values),
|
values => updateAlias(id, values),
|
||||||
{
|
{
|
||||||
onSuccess: newAlias => {
|
onSuccess: async newAlias => {
|
||||||
;(newAlias as any as DecryptedAlias).notes = decryptAliasNotes(
|
;(newAlias as any as DecryptedAlias).notes = decryptAliasNotes(
|
||||||
newAlias.encryptedNotes,
|
newAlias.encryptedNotes,
|
||||||
_decryptUsingMasterPassword,
|
_decryptUsingMasterPassword,
|
||||||
@ -80,7 +81,9 @@ export default function AliasNotesForm({id, notes, onChanged}: AliasNotesFormPro
|
|||||||
|
|
||||||
showSuccess(t("relations.alias.mutations.success.notesUpdated"))
|
showSuccess(t("relations.alias.mutations.success.notesUpdated"))
|
||||||
|
|
||||||
onChanged(newAlias as any as DecryptedAlias)
|
await queryClient.cancelQueries(queryKey)
|
||||||
|
|
||||||
|
queryClient.setQueryData<DecryptedAlias | Alias>(queryKey, newAlias)
|
||||||
},
|
},
|
||||||
onError: showError,
|
onError: showError,
|
||||||
},
|
},
|
||||||
|
@ -10,7 +10,7 @@ import {useTranslation} from "react-i18next"
|
|||||||
import {LoadingButton} from "@mui/lab"
|
import {LoadingButton} from "@mui/lab"
|
||||||
import {Box, Collapse, Grid, Typography} from "@mui/material"
|
import {Box, Collapse, Grid, Typography} from "@mui/material"
|
||||||
import {mdiTextBoxMultiple} from "@mdi/js/commonjs/mdi"
|
import {mdiTextBoxMultiple} from "@mdi/js/commonjs/mdi"
|
||||||
import {useMutation} from "@tanstack/react-query"
|
import {QueryKey, useMutation} from "@tanstack/react-query"
|
||||||
import Icon from "@mdi/react"
|
import Icon from "@mdi/react"
|
||||||
|
|
||||||
import {Alias, DecryptedAlias, ImageProxyFormatType, ProxyUserAgentType} from "~/server-types"
|
import {Alias, DecryptedAlias, ImageProxyFormatType, ProxyUserAgentType} from "~/server-types"
|
||||||
@ -21,6 +21,7 @@ import {
|
|||||||
PROXY_USER_AGENT_TYPE_NAME_MAP,
|
PROXY_USER_AGENT_TYPE_NAME_MAP,
|
||||||
} from "~/constants/enum-mappings"
|
} from "~/constants/enum-mappings"
|
||||||
import {useErrorSuccessSnacks} from "~/hooks"
|
import {useErrorSuccessSnacks} from "~/hooks"
|
||||||
|
import {queryClient} from "~/constants/react-query"
|
||||||
import AuthContext from "~/AuthContext/AuthContext"
|
import AuthContext from "~/AuthContext/AuthContext"
|
||||||
import FormikAutoLockNavigation from "~/LockNavigationContext/FormikAutoLockNavigation"
|
import FormikAutoLockNavigation from "~/LockNavigationContext/FormikAutoLockNavigation"
|
||||||
import SelectField from "~/route-widgets/SettingsRoute/SelectField"
|
import SelectField from "~/route-widgets/SettingsRoute/SelectField"
|
||||||
@ -29,7 +30,7 @@ import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes"
|
|||||||
export interface AliasPreferencesFormProps {
|
export interface AliasPreferencesFormProps {
|
||||||
alias: Alias | DecryptedAlias
|
alias: Alias | DecryptedAlias
|
||||||
|
|
||||||
onChanged: (newAlias: Alias | DecryptedAlias) => void
|
queryKey: QueryKey
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Form {
|
interface Form {
|
||||||
@ -44,7 +45,7 @@ interface Form {
|
|||||||
|
|
||||||
export default function AliasPreferencesForm({
|
export default function AliasPreferencesForm({
|
||||||
alias,
|
alias,
|
||||||
onChanged,
|
queryKey,
|
||||||
}: AliasPreferencesFormProps): ReactElement {
|
}: AliasPreferencesFormProps): ReactElement {
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const {showSuccess, showError} = useErrorSuccessSnacks()
|
const {showSuccess, showError} = useErrorSuccessSnacks()
|
||||||
@ -77,14 +78,16 @@ export default function AliasPreferencesForm({
|
|||||||
const {mutateAsync} = useMutation<Alias, AxiosError, UpdateAliasData>(
|
const {mutateAsync} = useMutation<Alias, AxiosError, UpdateAliasData>(
|
||||||
data => updateAlias(alias.id, data),
|
data => updateAlias(alias.id, data),
|
||||||
{
|
{
|
||||||
onSuccess: alias => {
|
onSuccess: async newAlias => {
|
||||||
showSuccess(t("relations.alias.mutations.success.aliasUpdated"))
|
showSuccess(t("relations.alias.mutations.success.aliasUpdated"))
|
||||||
;(alias as any as DecryptedAlias).notes = decryptAliasNotes(
|
;(newAlias as any as DecryptedAlias).notes = decryptAliasNotes(
|
||||||
alias.encryptedNotes,
|
newAlias.encryptedNotes,
|
||||||
_decryptUsingMasterPassword,
|
_decryptUsingMasterPassword,
|
||||||
)
|
)
|
||||||
|
|
||||||
onChanged(alias)
|
await queryClient.cancelQueries(queryKey)
|
||||||
|
|
||||||
|
queryClient.setQueryData<DecryptedAlias | Alias>(queryKey, newAlias)
|
||||||
},
|
},
|
||||||
onError: showError,
|
onError: showError,
|
||||||
},
|
},
|
||||||
|
@ -1,37 +1,54 @@
|
|||||||
import {ReactElement, useContext} from "react"
|
import {ReactElement, useContext} from "react"
|
||||||
import {AxiosError} from "axios"
|
import {AxiosError} from "axios"
|
||||||
import {useTranslation} from "react-i18next"
|
import {useTranslation} from "react-i18next"
|
||||||
|
import update from "immutability-helper"
|
||||||
|
|
||||||
import {Switch} from "@mui/material"
|
import {Switch} from "@mui/material"
|
||||||
import {useMutation} from "@tanstack/react-query"
|
import {QueryKey, useMutation} from "@tanstack/react-query"
|
||||||
|
|
||||||
import {Alias, DecryptedAlias} from "~/server-types"
|
import {Alias, DecryptedAlias} from "~/server-types"
|
||||||
import {UpdateAliasData, updateAlias} from "~/apis"
|
import {UpdateAliasData, updateAlias} from "~/apis"
|
||||||
import {useErrorSuccessSnacks, useUIState} from "~/hooks"
|
import {useErrorSuccessSnacks, useUIState} from "~/hooks"
|
||||||
|
import {queryClient} from "~/constants/react-query"
|
||||||
import AuthContext, {EncryptionStatus} from "~/AuthContext/AuthContext"
|
import AuthContext, {EncryptionStatus} from "~/AuthContext/AuthContext"
|
||||||
import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes"
|
import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes"
|
||||||
|
|
||||||
export interface ChangeAliasActivationStatusSwitchProps {
|
export interface ChangeAliasActivationStatusSwitchProps {
|
||||||
id: string
|
id: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
|
queryKey: QueryKey
|
||||||
onChanged: (alias: Alias | DecryptedAlias) => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ChangeAliasActivationStatusSwitch({
|
export default function ChangeAliasActivationStatusSwitch({
|
||||||
id,
|
id,
|
||||||
isActive,
|
isActive,
|
||||||
onChanged,
|
queryKey,
|
||||||
}: ChangeAliasActivationStatusSwitchProps): ReactElement {
|
}: ChangeAliasActivationStatusSwitchProps): ReactElement {
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const {showError, showSuccess} = useErrorSuccessSnacks()
|
const {showError, showSuccess} = useErrorSuccessSnacks()
|
||||||
const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext)
|
const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext)
|
||||||
|
|
||||||
const [isActiveUIState, setIsActiveUIState] = useUIState<boolean>(isActive)
|
const {mutateAsync, isLoading} = useMutation<
|
||||||
|
Alias,
|
||||||
|
AxiosError,
|
||||||
|
UpdateAliasData,
|
||||||
|
{previousAlias: DecryptedAlias | Alias | undefined}
|
||||||
|
>(values => updateAlias(id, values), {
|
||||||
|
onMutate: async values => {
|
||||||
|
await queryClient.cancelQueries(queryKey)
|
||||||
|
|
||||||
const {mutateAsync, isLoading} = useMutation<Alias, AxiosError, UpdateAliasData>(
|
const previousAlias = queryClient.getQueryData<DecryptedAlias | Alias>(queryKey)
|
||||||
values => updateAlias(id, values),
|
|
||||||
{
|
queryClient.setQueryData<DecryptedAlias | Alias>(queryKey, old =>
|
||||||
|
update(old, {
|
||||||
|
isActive: {
|
||||||
|
$set: values.isActive!,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
return {previousAlias}
|
||||||
|
},
|
||||||
onSuccess: newAlias => {
|
onSuccess: newAlias => {
|
||||||
if (encryptionStatus === EncryptionStatus.Available) {
|
if (encryptionStatus === EncryptionStatus.Available) {
|
||||||
;(newAlias as any as DecryptedAlias).notes = decryptAliasNotes(
|
;(newAlias as any as DecryptedAlias).notes = decryptAliasNotes(
|
||||||
@ -40,26 +57,32 @@ export default function ChangeAliasActivationStatusSwitch({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onChanged(newAlias)
|
queryClient.setQueryData<DecryptedAlias | Alias>(queryKey, newAlias)
|
||||||
},
|
},
|
||||||
onError: showError,
|
onError: (error, values, context) => {
|
||||||
|
showError(error)
|
||||||
|
|
||||||
|
if (context?.previousAlias) {
|
||||||
|
queryClient.setQueryData<DecryptedAlias | Alias>(queryKey, context.previousAlias)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Switch
|
||||||
checked={isActiveUIState}
|
checked={isActive}
|
||||||
disabled={isActiveUIState === null || isLoading}
|
|
||||||
onChange={async () => {
|
onChange={async () => {
|
||||||
setIsActiveUIState(!isActiveUIState)
|
if (isLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await mutateAsync({
|
await mutateAsync({
|
||||||
isActive: !isActiveUIState,
|
isActive: !isActive,
|
||||||
})
|
})
|
||||||
|
|
||||||
showSuccess(
|
showSuccess(
|
||||||
isActiveUIState
|
isActive
|
||||||
? t("relations.alias.mutations.success.aliasChangedToDisabled")
|
? t("relations.alias.mutations.success.aliasChangedToDisabled")
|
||||||
: t("relations.alias.mutations.success.aliasChangedToEnabled"),
|
: t("relations.alias.mutations.success.aliasChangedToEnabled"),
|
||||||
)
|
)
|
||||||
|
@ -19,17 +19,14 @@ import {
|
|||||||
import {useMutation} from "@tanstack/react-query"
|
import {useMutation} from "@tanstack/react-query"
|
||||||
|
|
||||||
import {CreateAliasData, createAlias} from "~/apis"
|
import {CreateAliasData, createAlias} from "~/apis"
|
||||||
import {Alias, AliasType} from "~/server-types"
|
import {Alias, AliasList, AliasType, PaginationResult} from "~/server-types"
|
||||||
import {DEFAULT_ALIAS_NOTE} from "~/constants/values"
|
import {DEFAULT_ALIAS_NOTE} from "~/constants/values"
|
||||||
import {useErrorSuccessSnacks} from "~/hooks"
|
import {useErrorSuccessSnacks} from "~/hooks"
|
||||||
|
import {queryClient} from "~/constants/react-query"
|
||||||
import AuthContext, {EncryptionStatus} from "~/AuthContext/AuthContext"
|
import AuthContext, {EncryptionStatus} from "~/AuthContext/AuthContext"
|
||||||
import CustomAliasDialog from "~/route-widgets/AliasesRoute/CustomAliasDialog"
|
import CustomAliasDialog from "~/route-widgets/AliasesRoute/CustomAliasDialog"
|
||||||
|
|
||||||
export interface CreateAliasButtonProps {
|
export function CreateAliasButton(): ReactElement {
|
||||||
onCreated: (alias: Alias) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function CreateAliasButton({onCreated}: CreateAliasButtonProps): ReactElement {
|
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const {showSuccess, showError} = useErrorSuccessSnacks()
|
const {showSuccess, showError} = useErrorSuccessSnacks()
|
||||||
const {_encryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext)
|
const {_encryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext)
|
||||||
@ -53,11 +50,28 @@ export default function CreateAliasButton({onCreated}: CreateAliasButtonProps):
|
|||||||
return createAlias(values)
|
return createAlias(values)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
onSuccess: alias => {
|
|
||||||
onCreated(alias)
|
|
||||||
showSuccess(t("relations.alias.mutations.success.aliasCreation"))
|
|
||||||
},
|
|
||||||
onError: showError,
|
onError: showError,
|
||||||
|
onSuccess: async alias => {
|
||||||
|
showSuccess(t("relations.alias.mutations.success.aliasCreation"))
|
||||||
|
|
||||||
|
await queryClient.cancelQueries({
|
||||||
|
queryKey: ["get_aliases", ""],
|
||||||
|
})
|
||||||
|
|
||||||
|
queryClient.setQueryData<PaginationResult<AliasList>>(["get_aliases", ""], old => {
|
||||||
|
if (old) {
|
||||||
|
return update(old, {
|
||||||
|
items: {
|
||||||
|
$unshift: [alias],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return old
|
||||||
|
})
|
||||||
|
|
||||||
|
return alias
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ export default function SelectField({
|
|||||||
|
|
||||||
formik.setFieldValue(name, value)
|
formik.setFieldValue(name, value)
|
||||||
}}
|
}}
|
||||||
disabled={formik.isSubmitting}
|
disabled={formik.sSubmitting}
|
||||||
error={Boolean(formik.touched[name] && formik.errors[name])}
|
error={Boolean(formik.touched[name] && formik.errors[name])}
|
||||||
renderValue={value =>
|
renderValue={value =>
|
||||||
value === "null" ? (
|
value === "null" ? (
|
||||||
|
@ -4,24 +4,33 @@ import {AxiosError} from "axios"
|
|||||||
import {useTranslation} from "react-i18next"
|
import {useTranslation} from "react-i18next"
|
||||||
|
|
||||||
import {useQuery} from "@tanstack/react-query"
|
import {useQuery} from "@tanstack/react-query"
|
||||||
|
import {Grid} from "@mui/material"
|
||||||
|
|
||||||
import {getAlias} from "~/apis"
|
import {getAlias} from "~/apis"
|
||||||
import {Alias, DecryptedAlias} from "~/server-types"
|
import {Alias, DecryptedAlias} from "~/server-types"
|
||||||
import {QueryResult, SimplePage} from "~/components"
|
import {
|
||||||
|
AliasTypeIndicator,
|
||||||
|
DecryptionPasswordMissingAlert,
|
||||||
|
QueryResult,
|
||||||
|
SimplePage,
|
||||||
|
SimplePageBuilder,
|
||||||
|
} from "~/components"
|
||||||
|
import AliasAddress from "~/route-widgets/AliasDetailRoute/AliasAddress"
|
||||||
import AliasDetails from "~/route-widgets/AliasDetailRoute/AliasDetails"
|
import AliasDetails from "~/route-widgets/AliasDetailRoute/AliasDetails"
|
||||||
|
import AliasNotesForm from "~/route-widgets/AliasDetailRoute/AliasNotesForm"
|
||||||
|
import AliasPreferencesForm from "~/route-widgets/AliasDetailRoute/AliasPreferencesForm"
|
||||||
import AuthContext, {EncryptionStatus} from "~/AuthContext/AuthContext"
|
import AuthContext, {EncryptionStatus} from "~/AuthContext/AuthContext"
|
||||||
|
import ChangeAliasActivationStatusSwitch from "~/route-widgets/AliasDetailRoute/ChangeAliasActivationStatusSwitch"
|
||||||
import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes"
|
import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes"
|
||||||
|
|
||||||
export default function AliasDetailRoute(): ReactElement {
|
export default function AliasDetailRoute(): ReactElement {
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
const address = atob(params.addressInBase64 as string)
|
const address = atob(params.addressInBase64 as string)
|
||||||
const {_decryptUsingMasterPassword, encryptionStatus} =
|
const queryKey = ["get_alias", address]
|
||||||
useContext(AuthContext)
|
const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext)
|
||||||
|
|
||||||
const query = useQuery<Alias | DecryptedAlias, AxiosError>(
|
const query = useQuery<Alias | DecryptedAlias, AxiosError>(queryKey, async () => {
|
||||||
["get_alias", params.addressInBase64],
|
|
||||||
async () => {
|
|
||||||
const alias = await getAlias(address)
|
const alias = await getAlias(address)
|
||||||
|
|
||||||
if (encryptionStatus === EncryptionStatus.Available) {
|
if (encryptionStatus === EncryptionStatus.Available) {
|
||||||
@ -32,13 +41,55 @@ export default function AliasDetailRoute(): ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return alias
|
return alias
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimplePage title={t("routes.AliasDetailRoute.title")}>
|
<SimplePage title={t("routes.AliasDetailRoute.title")}>
|
||||||
<QueryResult<Alias | DecryptedAlias> query={query}>
|
<QueryResult<Alias | DecryptedAlias> query={query}>
|
||||||
{alias => <AliasDetails alias={alias} />}
|
{alias => (
|
||||||
|
<SimplePageBuilder.MultipleSections>
|
||||||
|
{[
|
||||||
|
<Grid
|
||||||
|
key="basic"
|
||||||
|
container
|
||||||
|
spacing={1}
|
||||||
|
direction="row"
|
||||||
|
alignItems="center"
|
||||||
|
>
|
||||||
|
<Grid item>
|
||||||
|
<AliasTypeIndicator type={alias.type} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<AliasAddress address={address} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<ChangeAliasActivationStatusSwitch
|
||||||
|
id={alias.id}
|
||||||
|
isActive={alias.isActive}
|
||||||
|
queryKey={queryKey}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>,
|
||||||
|
<div key="notes">
|
||||||
|
{encryptionStatus === EncryptionStatus.Available ? (
|
||||||
|
<AliasNotesForm
|
||||||
|
id={alias.id}
|
||||||
|
notes={(alias as DecryptedAlias).notes}
|
||||||
|
queryKey={queryKey}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DecryptionPasswordMissingAlert />
|
||||||
|
)}
|
||||||
|
</div>,
|
||||||
|
<SimplePageBuilder.Section
|
||||||
|
label={t("routes.AliasDetailRoute.sections.settings.title")}
|
||||||
|
key="settings"
|
||||||
|
>
|
||||||
|
<AliasPreferencesForm alias={alias} queryKey={queryKey} />
|
||||||
|
</SimplePageBuilder.Section>,
|
||||||
|
]}
|
||||||
|
</SimplePageBuilder.MultipleSections>
|
||||||
|
)}
|
||||||
</QueryResult>
|
</QueryResult>
|
||||||
</SimplePage>
|
</SimplePage>
|
||||||
)
|
)
|
||||||
|
@ -2,13 +2,17 @@ import {ReactElement, useState, useTransition} from "react"
|
|||||||
import {AxiosError} from "axios"
|
import {AxiosError} from "axios"
|
||||||
import {MdSearch} from "react-icons/md"
|
import {MdSearch} from "react-icons/md"
|
||||||
import {useTranslation} from "react-i18next"
|
import {useTranslation} from "react-i18next"
|
||||||
|
import {useCopyToClipboard, useKeyPress, useUpdateEffect} from "react-use"
|
||||||
|
|
||||||
import {useQuery} from "@tanstack/react-query"
|
import {useQuery} from "@tanstack/react-query"
|
||||||
import {InputAdornment, TextField} from "@mui/material"
|
import {Alert, Grid, InputAdornment, List, Snackbar, TextField} from "@mui/material"
|
||||||
|
|
||||||
import {AliasList, PaginationResult} from "~/server-types"
|
import {AliasList, PaginationResult} from "~/server-types"
|
||||||
import {QueryResult, SimplePage} from "~/components"
|
import {ErrorSnack, NoSearchResults, QueryResult, SimplePage, SuccessSnack} from "~/components"
|
||||||
import AliasesDetails from "~/route-widgets/AliasesRoute/AliasesDetails"
|
import {useIsAnyInputFocused} from "~/hooks"
|
||||||
|
import {CreateAliasButton} from "~/route-widgets/AliasesRoute/CreateAliasButton"
|
||||||
|
import AliasesListItem from "~/route-widgets/AliasesRoute/AliasesListItem"
|
||||||
|
import EmptyStateScreen from "~/route-widgets/AliasesRoute/EmptyStateScreen"
|
||||||
import getAliases from "~/apis/get-aliases"
|
import getAliases from "~/apis/get-aliases"
|
||||||
|
|
||||||
export default function AliasesRoute(): ReactElement {
|
export default function AliasesRoute(): ReactElement {
|
||||||
@ -19,6 +23,12 @@ export default function AliasesRoute(): ReactElement {
|
|||||||
const [, startTransition] = useTransition()
|
const [, startTransition] = useTransition()
|
||||||
const [showSearch, setShowSearch] = useState<boolean>(false)
|
const [showSearch, setShowSearch] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const [{value, error}, copyToClipboard] = useCopyToClipboard()
|
||||||
|
const [isPressingControl] = useKeyPress("Shift")
|
||||||
|
const isAnyInputFocused = useIsAnyInputFocused()
|
||||||
|
const [lockDisabledCopyMode, setLockDisabledCopyMode] = useState<boolean>(false)
|
||||||
|
const isInCopyAddressMode = !isAnyInputFocused && !lockDisabledCopyMode && isPressingControl
|
||||||
|
|
||||||
const query = useQuery<PaginationResult<AliasList>, AxiosError>(
|
const query = useQuery<PaginationResult<AliasList>, AxiosError>(
|
||||||
["get_aliases", queryValue],
|
["get_aliases", queryValue],
|
||||||
() =>
|
() =>
|
||||||
@ -34,6 +44,12 @@ export default function AliasesRoute(): ReactElement {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useUpdateEffect(() => {
|
||||||
|
if (!isPressingControl) {
|
||||||
|
setLockDisabledCopyMode(false)
|
||||||
|
}
|
||||||
|
}, [isPressingControl])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimplePage
|
<SimplePage
|
||||||
title={t("routes.AliasesRoute.title")}
|
title={t("routes.AliasesRoute.title")}
|
||||||
@ -63,8 +79,57 @@ export default function AliasesRoute(): ReactElement {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<QueryResult<PaginationResult<AliasList>, AxiosError> query={query}>
|
<QueryResult<PaginationResult<AliasList>, AxiosError> query={query}>
|
||||||
{result => (
|
{({items: aliases}) => (
|
||||||
<AliasesDetails aliases={result.items} isSearching={searchValue !== ""} />
|
<>
|
||||||
|
<Grid container spacing={4} direction="column">
|
||||||
|
<Grid item>
|
||||||
|
{(() => {
|
||||||
|
if (aliases.length === 0) {
|
||||||
|
if (searchValue === "") {
|
||||||
|
return <EmptyStateScreen />
|
||||||
|
} else {
|
||||||
|
return <NoSearchResults />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<List>
|
||||||
|
{aliases.map(alias => (
|
||||||
|
<AliasesListItem
|
||||||
|
alias={alias}
|
||||||
|
key={alias.id}
|
||||||
|
onCopy={
|
||||||
|
isInCopyAddressMode
|
||||||
|
? alias => {
|
||||||
|
copyToClipboard(alias)
|
||||||
|
setLockDisabledCopyMode(true)
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
)
|
||||||
|
})()}
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<CreateAliasButton />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<SuccessSnack
|
||||||
|
key={value}
|
||||||
|
message={
|
||||||
|
value &&
|
||||||
|
t("relations.alias.mutations.success.addressCopiedToClipboard")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ErrorSnack message={error && t("general.copyError")} />
|
||||||
|
<Snackbar open={isInCopyAddressMode} autoHideDuration={null}>
|
||||||
|
<Alert variant="standard" severity="info">
|
||||||
|
{t("routes.AliasesRoute.isInCopyMode")}
|
||||||
|
</Alert>
|
||||||
|
</Snackbar>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</QueryResult>
|
</QueryResult>
|
||||||
</SimplePage>
|
</SimplePage>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user