mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-19 15:55:26 +02:00
added AliasNotesForm.tsx
This commit is contained in:
parent
12ac8f9a0b
commit
c0e48cb62a
25
src/components/BackupImage.tsx
Normal file
25
src/components/BackupImage.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React, {ReactElement, useState} from "react"
|
||||||
|
|
||||||
|
export interface BackupImageProps
|
||||||
|
extends Omit<
|
||||||
|
React.DetailedHTMLProps<
|
||||||
|
React.ImgHTMLAttributes<HTMLImageElement>,
|
||||||
|
HTMLImageElement
|
||||||
|
>,
|
||||||
|
"src"
|
||||||
|
> {
|
||||||
|
fallbackSrc: string
|
||||||
|
src: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BackupImage({
|
||||||
|
fallbackSrc,
|
||||||
|
src,
|
||||||
|
...props
|
||||||
|
}: BackupImageProps): ReactElement {
|
||||||
|
const [source, setSource] = useState<string>(src)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img {...props} src={source} onError={() => setSource(fallbackSrc)} />
|
||||||
|
)
|
||||||
|
}
|
31
src/components/DecryptionPasswordMissingAlert.tsx
Normal file
31
src/components/DecryptionPasswordMissingAlert.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import {ReactElement, useContext} from "react"
|
||||||
|
import {MdLock} from "react-icons/md"
|
||||||
|
import {Link as RouterLink} from "react-router-dom"
|
||||||
|
|
||||||
|
import {Alert, Button, Grid} from "@mui/material"
|
||||||
|
|
||||||
|
import LockNavigationContext from "~/LockNavigationContext/LockNavigationContext"
|
||||||
|
|
||||||
|
export default function DecryptionPasswordMissingAlert(): ReactElement {
|
||||||
|
const {handleAnchorClick} = useContext(LockNavigationContext)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container spacing={2} direction="column" alignItems="center">
|
||||||
|
<Grid item>
|
||||||
|
<Alert severity="warning">
|
||||||
|
Your decryption password is required to view this section.
|
||||||
|
</Alert>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<Button
|
||||||
|
startIcon={<MdLock />}
|
||||||
|
component={RouterLink}
|
||||||
|
to="/enter-password"
|
||||||
|
onClick={handleAnchorClick}
|
||||||
|
>
|
||||||
|
Enter password
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
)
|
||||||
|
}
|
@ -22,3 +22,11 @@ export * from "./DecryptReport"
|
|||||||
export {default as DecryptReport} from "./DecryptReport"
|
export {default as DecryptReport} from "./DecryptReport"
|
||||||
export * from "./SimplePage"
|
export * from "./SimplePage"
|
||||||
export {default as SimplePage} from "./SimplePage"
|
export {default as SimplePage} from "./SimplePage"
|
||||||
|
export * from "./QueryResult"
|
||||||
|
export {default as QueryResult} from "./QueryResult"
|
||||||
|
export * from "./AliasTypeIndicator"
|
||||||
|
export {default as AliasTypeIndicator} from "./AliasTypeIndicator"
|
||||||
|
export * from "./DecryptionPasswordMissingAlert"
|
||||||
|
export {default as DecryptionPasswordMissingAlert} from "./DecryptionPasswordMissingAlert"
|
||||||
|
export * from "./BackupImage"
|
||||||
|
export {default as BackupImage} from "./BackupImage"
|
||||||
|
@ -1,2 +1,13 @@
|
|||||||
|
import {AliasNote} from "~/server-types"
|
||||||
|
|
||||||
export const MASTER_PASSWORD_LENGTH = 4096
|
export const MASTER_PASSWORD_LENGTH = 4096
|
||||||
export const LOCAL_REGEX = /^[a-zA-Z0-9!#$%&‘*+–/=?^_`.{|}~-]{1,64}$/g
|
export const LOCAL_REGEX = /^[a-zA-Z0-9!#$%&‘*+–/=?^_`.{|}~-]{1,64}$/g
|
||||||
|
export const URL_REGEX =
|
||||||
|
/((http[s]*:\/\/)?[a-z0-9-%\/\&=?\.]+\.[a-z]{2,4}\/?([^\s<>\#%"\,\{\}\\|\\\^\[\]`]+)?)/gi
|
||||||
|
export const DEFAULT_ALIAS_NOTE: AliasNote = {
|
||||||
|
version: "1.0",
|
||||||
|
data: {
|
||||||
|
personalNotes: "",
|
||||||
|
websites: [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
266
src/route-widgets/AliasDetailRoute/AliasNotesForm.tsx
Normal file
266
src/route-widgets/AliasDetailRoute/AliasNotesForm.tsx
Normal file
@ -0,0 +1,266 @@
|
|||||||
|
import * as yup from "yup"
|
||||||
|
import {TiDelete} from "react-icons/ti"
|
||||||
|
import {AxiosError} from "axios"
|
||||||
|
import {ReactElement, useContext} from "react"
|
||||||
|
import {RiLinkM, RiStickyNoteFill} from "react-icons/ri"
|
||||||
|
import {FieldArray, FormikProvider, useFormik} from "formik"
|
||||||
|
|
||||||
|
import {useMutation} from "@tanstack/react-query"
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormGroup,
|
||||||
|
FormHelperText,
|
||||||
|
Grid,
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemSecondaryAction,
|
||||||
|
ListItemText,
|
||||||
|
TextField,
|
||||||
|
} from "@mui/material"
|
||||||
|
|
||||||
|
import {URL_REGEX} from "~/constants/values"
|
||||||
|
import {parseFastAPIError, whenEnterPressed} from "~/utils"
|
||||||
|
import {BackupImage, ErrorSnack, SuccessSnack} from "~/components"
|
||||||
|
import {Alias, AliasNote} from "~/server-types"
|
||||||
|
import {UpdateAliasData, updateAlias} from "~/apis"
|
||||||
|
import AuthContext from "~/AuthContext/AuthContext"
|
||||||
|
|
||||||
|
export interface AliasNotesFormProps {
|
||||||
|
id: string
|
||||||
|
notes: AliasNote
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Form {
|
||||||
|
personalNotes: string
|
||||||
|
websites: AliasNote["data"]["websites"]
|
||||||
|
|
||||||
|
detail?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WebsiteForm {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const SCHEMA = yup.object().shape({
|
||||||
|
personalNotes: yup.string(),
|
||||||
|
websites: yup.array().of(
|
||||||
|
yup.object().shape({
|
||||||
|
url: yup.string().url(),
|
||||||
|
createdAt: yup.date(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
})
|
||||||
|
const WEBSITE_SCHEMA = yup.object().shape({
|
||||||
|
url: yup.string().matches(URL_REGEX, "This URL is invalid."),
|
||||||
|
})
|
||||||
|
|
||||||
|
const getDomain = (url: string): string => {
|
||||||
|
const {hostname, port} = new URL(url)
|
||||||
|
return `${hostname}${port ? `:${port}` : ""}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AliasNotesForm({
|
||||||
|
id,
|
||||||
|
notes,
|
||||||
|
}: AliasNotesFormProps): ReactElement {
|
||||||
|
const {_encryptUsingMasterPassword} = useContext(AuthContext)
|
||||||
|
const {mutateAsync, isSuccess} = useMutation<
|
||||||
|
Alias,
|
||||||
|
AxiosError,
|
||||||
|
UpdateAliasData
|
||||||
|
>(values => updateAlias(id, values))
|
||||||
|
const formik = useFormik<Form>({
|
||||||
|
validationSchema: SCHEMA,
|
||||||
|
initialValues: {
|
||||||
|
personalNotes: notes.data.personalNotes,
|
||||||
|
websites: notes.data.websites,
|
||||||
|
},
|
||||||
|
onSubmit: async (values, {setErrors}) => {
|
||||||
|
try {
|
||||||
|
const newNotes = {
|
||||||
|
...notes,
|
||||||
|
data: {
|
||||||
|
...notes.data,
|
||||||
|
personalNotes: values.personalNotes,
|
||||||
|
websites: values.websites,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const data = _encryptUsingMasterPassword(
|
||||||
|
JSON.stringify(newNotes),
|
||||||
|
)
|
||||||
|
await mutateAsync({
|
||||||
|
encryptedNotes: data,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
setErrors(parseFastAPIError(error as AxiosError))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const websiteFormik = useFormik<WebsiteForm>({
|
||||||
|
validationSchema: WEBSITE_SCHEMA,
|
||||||
|
initialValues: {
|
||||||
|
url: "",
|
||||||
|
},
|
||||||
|
onSubmit: async values => {
|
||||||
|
const url = (() => {
|
||||||
|
// Make sure url starts with `http://` or `https://`
|
||||||
|
if (values.url.startsWith("http://")) {
|
||||||
|
return values.url
|
||||||
|
}
|
||||||
|
if (values.url.startsWith("https://")) {
|
||||||
|
return values.url
|
||||||
|
}
|
||||||
|
return `https://${values.url}`
|
||||||
|
})()
|
||||||
|
|
||||||
|
const {hostname, protocol, port} = new URL(url)
|
||||||
|
const baseUrl = `${protocol}//${hostname}${port ? `:${port}` : ""}`
|
||||||
|
|
||||||
|
websiteFormik.resetForm()
|
||||||
|
await formik.setFieldValue(
|
||||||
|
"websites",
|
||||||
|
[
|
||||||
|
...formik.values.websites,
|
||||||
|
{
|
||||||
|
url: baseUrl,
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
await formik.submitForm()
|
||||||
|
},
|
||||||
|
validateOnChange: true,
|
||||||
|
validateOnBlur: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<Grid container spacing={4} direction="column">
|
||||||
|
<Grid item>
|
||||||
|
<TextField
|
||||||
|
label="Personal Notes"
|
||||||
|
multiline
|
||||||
|
fullWidth
|
||||||
|
key="personalNotes"
|
||||||
|
id="personalNotes"
|
||||||
|
name="personalNotes"
|
||||||
|
value={formik.values.personalNotes}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
onBlur={() => formik.submitForm()}
|
||||||
|
disabled={formik.isSubmitting}
|
||||||
|
error={
|
||||||
|
formik.touched.personalNotes &&
|
||||||
|
Boolean(formik.errors.personalNotes)
|
||||||
|
}
|
||||||
|
helperText={
|
||||||
|
(formik.touched.personalNotes &&
|
||||||
|
formik.errors.personalNotes) ||
|
||||||
|
"You can enter personal notes for this alias here. Notes are encrypted."
|
||||||
|
}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<RiStickyNoteFill />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
<FormGroup row>
|
||||||
|
<TextField
|
||||||
|
name="url"
|
||||||
|
id="url"
|
||||||
|
label="Website"
|
||||||
|
variant="outlined"
|
||||||
|
value={websiteFormik.values.url}
|
||||||
|
onChange={websiteFormik.handleChange}
|
||||||
|
onBlur={websiteFormik.handleBlur}
|
||||||
|
onKeyDown={whenEnterPressed(() =>
|
||||||
|
websiteFormik.handleSubmit(),
|
||||||
|
)}
|
||||||
|
disabled={websiteFormik.isSubmitting}
|
||||||
|
error={
|
||||||
|
websiteFormik.touched.url &&
|
||||||
|
Boolean(websiteFormik.errors.url)
|
||||||
|
}
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<RiLinkM />
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="contained"
|
||||||
|
disableElevation
|
||||||
|
onClick={() => websiteFormik.handleSubmit()}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</Button>
|
||||||
|
<FormHelperText
|
||||||
|
error={
|
||||||
|
websiteFormik.touched.url &&
|
||||||
|
Boolean(websiteFormik.errors.url)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{(websiteFormik.touched.url &&
|
||||||
|
websiteFormik.errors.url) ||
|
||||||
|
"Add a website to this alias. Used to autofill."}
|
||||||
|
</FormHelperText>
|
||||||
|
</FormGroup>
|
||||||
|
<FormikProvider value={formik}>
|
||||||
|
<FieldArray
|
||||||
|
name="websites"
|
||||||
|
render={arrayHelpers => (
|
||||||
|
<List>
|
||||||
|
{formik.values.websites.map(
|
||||||
|
(website, index) => (
|
||||||
|
<ListItem key={website.url}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<BackupImage
|
||||||
|
width={20}
|
||||||
|
fallbackSrc={`https://external-content.duckduckgo.com/ip3/${getDomain(
|
||||||
|
website.url,
|
||||||
|
)}.ico`}
|
||||||
|
src={`${website.url}/favicon.ico`}
|
||||||
|
/>
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText>
|
||||||
|
{website.url}
|
||||||
|
</ListItemText>
|
||||||
|
<ListItemSecondaryAction>
|
||||||
|
<IconButton
|
||||||
|
edge="end"
|
||||||
|
aria-label="delete"
|
||||||
|
onClick={() =>
|
||||||
|
arrayHelpers.remove(
|
||||||
|
index,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TiDelete />
|
||||||
|
</IconButton>
|
||||||
|
</ListItemSecondaryAction>
|
||||||
|
</ListItem>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormikProvider>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</form>
|
||||||
|
<ErrorSnack message={formik.errors.detail} />
|
||||||
|
<SuccessSnack message={isSuccess && "Update Alias notes!"} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -12,7 +12,12 @@ import {mdiTextBoxMultiple} from "@mdi/js/commonjs/mdi"
|
|||||||
import {useMutation} from "@tanstack/react-query"
|
import {useMutation} from "@tanstack/react-query"
|
||||||
import Icon from "@mdi/react"
|
import Icon from "@mdi/react"
|
||||||
|
|
||||||
import {Alias, ImageProxyFormatType, ProxyUserAgentType} from "~/server-types"
|
import {
|
||||||
|
Alias,
|
||||||
|
DecryptedAlias,
|
||||||
|
ImageProxyFormatType,
|
||||||
|
ProxyUserAgentType,
|
||||||
|
} from "~/server-types"
|
||||||
import {
|
import {
|
||||||
IMAGE_PROXY_FORMAT_TYPE_NAME_MAP,
|
IMAGE_PROXY_FORMAT_TYPE_NAME_MAP,
|
||||||
IMAGE_PROXY_USER_AGENT_TYPE_NAME_MAP,
|
IMAGE_PROXY_USER_AGENT_TYPE_NAME_MAP,
|
||||||
@ -24,7 +29,7 @@ import FormikAutoLockNavigation from "~/LockNavigationContext/FormikAutoLockNavi
|
|||||||
import SelectField from "~/route-widgets/SettingsRoute/SelectField"
|
import SelectField from "~/route-widgets/SettingsRoute/SelectField"
|
||||||
|
|
||||||
export interface AliasPreferencesFormProps {
|
export interface AliasPreferencesFormProps {
|
||||||
alias: Alias
|
alias: Alias | DecryptedAlias
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Form {
|
interface Form {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {ReactElement, useState} from "react"
|
import {ReactElement, useContext, useState} from "react"
|
||||||
import {useParams} from "react-router-dom"
|
import {useParams} from "react-router-dom"
|
||||||
import {AxiosError} from "axios"
|
import {AxiosError} from "axios"
|
||||||
|
|
||||||
@ -6,25 +6,41 @@ import {useMutation, useQuery} from "@tanstack/react-query"
|
|||||||
import {Grid, Switch, Typography} from "@mui/material"
|
import {Grid, Switch, Typography} from "@mui/material"
|
||||||
|
|
||||||
import {UpdateAliasData, getAlias, updateAlias} from "~/apis"
|
import {UpdateAliasData, getAlias, updateAlias} from "~/apis"
|
||||||
import {Alias} from "~/server-types"
|
import {Alias, DecryptedAlias} from "~/server-types"
|
||||||
import {ErrorSnack, SuccessSnack} from "~/components"
|
import {
|
||||||
import {parseFastAPIError} from "~/utils"
|
AliasTypeIndicator,
|
||||||
|
ErrorSnack,
|
||||||
|
QueryResult,
|
||||||
|
SimplePage,
|
||||||
|
SuccessSnack,
|
||||||
|
} from "~/components"
|
||||||
|
import {decryptAliasNotes, parseFastAPIError} from "~/utils"
|
||||||
|
import AliasNotesForm from "~/route-widgets/AliasDetailRoute/AliasNotesForm"
|
||||||
import AliasPreferencesForm from "~/route-widgets/AliasDetailRoute/AliasPreferencesForm"
|
import AliasPreferencesForm from "~/route-widgets/AliasDetailRoute/AliasPreferencesForm"
|
||||||
import AliasTypeIndicator from "~/components/AliasTypeIndicator"
|
import AuthContext from "~/AuthContext/AuthContext"
|
||||||
import QueryResult from "~/components/QueryResult"
|
import DecryptionPasswordMissingAlert from "~/components/DecryptionPasswordMissingAlert"
|
||||||
import SimplePage from "~/components/SimplePage"
|
|
||||||
|
|
||||||
export default function AliasDetailRoute(): ReactElement {
|
export default function AliasDetailRoute(): ReactElement {
|
||||||
const params = useParams()
|
const params = useParams()
|
||||||
|
const {user, _decryptUsingMasterPassword} = useContext(AuthContext)
|
||||||
const address = atob(params.addressInBase64 as string)
|
const address = atob(params.addressInBase64 as string)
|
||||||
|
|
||||||
const [successMessage, setSuccessMessage] = useState<string>("")
|
const [successMessage, setSuccessMessage] = useState<string>("")
|
||||||
const [errorMessage, setErrorMessage] = useState<string>("")
|
const [errorMessage, setErrorMessage] = useState<string>("")
|
||||||
|
|
||||||
const [isActive, setIsActive] = useState<boolean>(true)
|
const [isActive, setIsActive] = useState<boolean>(true)
|
||||||
const query = useQuery<Alias, AxiosError>(
|
const query = useQuery<Alias | DecryptedAlias, AxiosError>(
|
||||||
["get_alias", params.addressInBase64],
|
["get_alias", params.addressInBase64],
|
||||||
() => getAlias(address),
|
async () => {
|
||||||
|
if (user?.encryptedPassword) {
|
||||||
|
return decryptAliasNotes(
|
||||||
|
await getAlias(address),
|
||||||
|
_decryptUsingMasterPassword,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return getAlias(address)
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
onSuccess: alias => setIsActive(alias.isActive),
|
onSuccess: alias => setIsActive(alias.isActive),
|
||||||
},
|
},
|
||||||
@ -33,16 +49,15 @@ export default function AliasDetailRoute(): ReactElement {
|
|||||||
values => updateAlias(query.data!.id, values),
|
values => updateAlias(query.data!.id, values),
|
||||||
{
|
{
|
||||||
onSuccess: () => query.refetch(),
|
onSuccess: () => query.refetch(),
|
||||||
onError: error => {
|
onError: error =>
|
||||||
setErrorMessage(parseFastAPIError(error).detail as string)
|
setErrorMessage(parseFastAPIError(error).detail as string),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SimplePage title="Alias Details">
|
<SimplePage title="Alias Details">
|
||||||
<QueryResult<Alias> query={query}>
|
<QueryResult<Alias | DecryptedAlias> query={query}>
|
||||||
{alias => (
|
{alias => (
|
||||||
<Grid container spacing={4}>
|
<Grid container spacing={4}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
@ -86,10 +101,33 @@ export default function AliasDetailRoute(): ReactElement {
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<Grid item width="100%">
|
||||||
|
<Grid container direction="column" spacing={4}>
|
||||||
|
<Grid item>
|
||||||
|
<Typography variant="h6" component="h3">
|
||||||
|
Notes
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item>
|
||||||
|
{user?.encryptedPassword &&
|
||||||
|
(alias as DecryptedAlias).notes ? (
|
||||||
|
<AliasNotesForm
|
||||||
|
id={alias.id}
|
||||||
|
notes={
|
||||||
|
(alias as DecryptedAlias)
|
||||||
|
.notes
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DecryptionPasswordMissingAlert />
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Grid container spacing={4}>
|
<Grid container spacing={4}>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6" component="h3">
|
||||||
Settings
|
Settings
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -92,6 +92,21 @@ export interface Alias {
|
|||||||
prefImageProxyUserAgent: ProxyUserAgentType | null
|
prefImageProxyUserAgent: ProxyUserAgentType | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AliasNote {
|
||||||
|
version: "1.0"
|
||||||
|
data: {
|
||||||
|
personalNotes: string
|
||||||
|
websites: Array<{
|
||||||
|
url: string
|
||||||
|
createdAt: Date
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DecryptedAlias extends Omit<Alias, "encryptedNotes"> {
|
||||||
|
notes: AliasNote
|
||||||
|
}
|
||||||
|
|
||||||
export interface AliasList {
|
export interface AliasList {
|
||||||
id: string
|
id: string
|
||||||
domain: string
|
domain: string
|
||||||
|
20
src/utils/decrypt-alias-notes.ts
Normal file
20
src/utils/decrypt-alias-notes.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {Alias, DecryptedAlias} from "~/server-types"
|
||||||
|
import {AuthContextType} from "~/AuthContext/AuthContext"
|
||||||
|
import {DEFAULT_ALIAS_NOTE} from "~/constants/values"
|
||||||
|
|
||||||
|
export default function decryptAliasNotes(
|
||||||
|
alias: Alias,
|
||||||
|
decryptContent: AuthContextType["_decryptUsingMasterPassword"],
|
||||||
|
): DecryptedAlias {
|
||||||
|
if (!alias.encryptedNotes) {
|
||||||
|
return {
|
||||||
|
...alias,
|
||||||
|
notes: DEFAULT_ALIAS_NOTE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...alias,
|
||||||
|
notes: JSON.parse(decryptContent(alias.encryptedNotes)),
|
||||||
|
}
|
||||||
|
}
|
@ -10,3 +10,7 @@ export * from "./build-encryption-password"
|
|||||||
export {default as buildEncryptionPassword} from "./build-encryption-password"
|
export {default as buildEncryptionPassword} from "./build-encryption-password"
|
||||||
export * from "./decrypt-string"
|
export * from "./decrypt-string"
|
||||||
export {default as decryptString} from "./decrypt-string"
|
export {default as decryptString} from "./decrypt-string"
|
||||||
|
export * from "./decrypt-alias-notes"
|
||||||
|
export {default as decryptAliasNotes} from "./decrypt-alias-notes"
|
||||||
|
export * from "./when-enter-pressed"
|
||||||
|
export {default as whenEnterPressed} from "./when-enter-pressed"
|
||||||
|
11
src/utils/when-enter-pressed.ts
Normal file
11
src/utils/when-enter-pressed.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import {KeyboardEventHandler} from "react"
|
||||||
|
|
||||||
|
export default function whenEnterPressed<T = HTMLDivElement>(
|
||||||
|
callback: KeyboardEventHandler<T>,
|
||||||
|
) {
|
||||||
|
return (event: any) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
callback(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user