Compare commits
11 Commits
feature/hi
...
feature/sw
Author | SHA1 | Date | |
---|---|---|---|
|
e3feb8b5a2 | ||
|
06fa8ffbd7 | ||
|
22753a8d89 | ||
|
425f7277fc | ||
|
ba3775e6c6 | ||
|
8c77b10b60 | ||
|
4ad66c8711 | ||
|
6a04c3ec4b | ||
|
6d72cf1f82 | ||
|
d199dcf9f7 | ||
|
0f28c80a15 |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Nsec.app",
|
"name": "Nsec.app - Nostr key management tool",
|
||||||
"short_name": "Nsec.app - Nostr key management tool",
|
"short_name": "Nsec.app",
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { DbKey, dbi } from './modules/db'
|
import { DbKey, dbi } from './modules/db'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { swicOnRender } from './modules/swic'
|
import { swicOnReload, swicOnRender } from './modules/swic'
|
||||||
import { useAppDispatch } from './store/hooks/redux'
|
import { useAppDispatch } from './store/hooks/redux'
|
||||||
import { setApps, setKeys, setPending, setPerms } from './store/reducers/content.slice'
|
import { setApps, setKeys, setPending, setPerms } from './store/reducers/content.slice'
|
||||||
import AppRoutes from './routes/AppRoutes'
|
import AppRoutes from './routes/AppRoutes'
|
||||||
@@ -77,6 +77,12 @@ function App() {
|
|||||||
setRender((r) => r + 1)
|
setRender((r) => r + 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// subscribe to service worker updates
|
||||||
|
swicOnReload(() => {
|
||||||
|
console.log('reload')
|
||||||
|
// FIXME show 'Please reload' badge at page top
|
||||||
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AppRoutes />
|
<AppRoutes />
|
||||||
|
174
src/components/Modal/ModalEditName/ModalEditName.tsx
Normal file
174
src/components/Modal/ModalEditName/ModalEditName.tsx
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import { CheckmarkIcon } from '@/assets'
|
||||||
|
import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar'
|
||||||
|
import { useModalSearchParams } from '@/hooks/useModalSearchParams'
|
||||||
|
import { swicCall } from '@/modules/swic'
|
||||||
|
import { Button } from '@/shared/Button/Button'
|
||||||
|
import { Input } from '@/shared/Input/Input'
|
||||||
|
import { LoadingSpinner } from '@/shared/LoadingSpinner/LoadingSpinner'
|
||||||
|
import { Modal } from '@/shared/Modal/Modal'
|
||||||
|
import { selectKeys } from '@/store'
|
||||||
|
import { useAppSelector } from '@/store/hooks/redux'
|
||||||
|
import { MODAL_PARAMS_KEYS } from '@/types/modal'
|
||||||
|
import { DOMAIN } from '@/utils/consts'
|
||||||
|
import { fetchNip05 } from '@/utils/helpers/helpers'
|
||||||
|
import { Stack, Typography, useTheme } from '@mui/material'
|
||||||
|
import { ChangeEvent, Fragment, useCallback, useEffect, useState } from 'react'
|
||||||
|
import { useParams, useSearchParams } from 'react-router-dom'
|
||||||
|
import { useDebounce } from 'use-debounce'
|
||||||
|
import { StyledSettingContainer } from './styled'
|
||||||
|
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
|
||||||
|
|
||||||
|
export const ModalEditName = () => {
|
||||||
|
const keys = useAppSelector(selectKeys)
|
||||||
|
const notify = useEnqueueSnackbar()
|
||||||
|
const { npub = '' } = useParams<{ npub: string }>()
|
||||||
|
|
||||||
|
const key = keys.find((k) => k.npub === npub)
|
||||||
|
const name = key?.name || ''
|
||||||
|
|
||||||
|
const { palette } = useTheme()
|
||||||
|
|
||||||
|
const { getModalOpened, createHandleCloseReplace } = useModalSearchParams()
|
||||||
|
const isModalOpened = getModalOpened(MODAL_PARAMS_KEYS.EDIT_NAME)
|
||||||
|
const handleCloseModal = createHandleCloseReplace(MODAL_PARAMS_KEYS.EDIT_NAME)
|
||||||
|
|
||||||
|
const [enteredName, setEnteredName] = useState('')
|
||||||
|
const [debouncedName] = useDebounce(enteredName, 300)
|
||||||
|
const isNameEqual = debouncedName === name
|
||||||
|
|
||||||
|
const [receiverNpub, setReceiverNpub] = useState('')
|
||||||
|
|
||||||
|
const [isAvailable, setIsAvailable] = useState(true)
|
||||||
|
const [isChecking, setIsChecking] = useState(false)
|
||||||
|
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
const [isTransferLoading, setIsTransferLoading] = useState(false)
|
||||||
|
|
||||||
|
const checkIsUsernameAvailable = useCallback(async () => {
|
||||||
|
if (!debouncedName.trim().length) return undefined
|
||||||
|
try {
|
||||||
|
setIsChecking(true)
|
||||||
|
const npubNip05 = await fetchNip05(`${debouncedName}@${DOMAIN}`)
|
||||||
|
setIsAvailable(!npubNip05 || npubNip05 === npub)
|
||||||
|
setIsChecking(false)
|
||||||
|
} catch (error) {
|
||||||
|
setIsAvailable(true)
|
||||||
|
setIsChecking(false)
|
||||||
|
}
|
||||||
|
}, [debouncedName])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
checkIsUsernameAvailable()
|
||||||
|
}, [checkIsUsernameAvailable])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setEnteredName(name)
|
||||||
|
return () => {
|
||||||
|
if (isModalOpened) {
|
||||||
|
setEnteredName('')
|
||||||
|
setReceiverNpub('')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line
|
||||||
|
}, [isModalOpened])
|
||||||
|
|
||||||
|
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => setEnteredName(e.target.value)
|
||||||
|
|
||||||
|
const handleReceiverNpubChange = (e: ChangeEvent<HTMLInputElement>) => setReceiverNpub(e.target.value)
|
||||||
|
|
||||||
|
const getInputHelperText = () => {
|
||||||
|
if (!debouncedName.trim().length || isNameEqual) return ''
|
||||||
|
if (isChecking) return 'Loading...'
|
||||||
|
if (!isAvailable) return 'Already taken'
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<CheckmarkIcon /> Available
|
||||||
|
</Fragment>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const inputHelperText = getInputHelperText()
|
||||||
|
|
||||||
|
const getHelperTextColor = useCallback(() => {
|
||||||
|
if (!debouncedName || isChecking || isNameEqual) return palette.textSecondaryDecorate.main
|
||||||
|
return isAvailable ? palette.success.main : palette.error.main
|
||||||
|
// deps
|
||||||
|
}, [debouncedName, isAvailable, isChecking, isNameEqual, palette])
|
||||||
|
|
||||||
|
const isNpubExists = npub.trim().length && keys.some((key) => key.npub === npub)
|
||||||
|
if (isModalOpened && !isNpubExists) {
|
||||||
|
handleCloseModal()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEditButtonDisabled = isNameEqual || !isAvailable || isChecking || isLoading || !enteredName.trim().length
|
||||||
|
const isTransferButtonDisabled = !name.length || !receiverNpub.trim().length || isTransferLoading
|
||||||
|
|
||||||
|
const handleEditName = async () => {
|
||||||
|
if (isEditButtonDisabled) return
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
await swicCall('editName', npub, enteredName)
|
||||||
|
notify('Username updated!', 'success')
|
||||||
|
setIsLoading(false)
|
||||||
|
} catch (error: any) {
|
||||||
|
setIsLoading(false)
|
||||||
|
notify(error?.message || 'Failed to edit username!', 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTransferName = async () => {
|
||||||
|
if (isTransferButtonDisabled) return
|
||||||
|
try {
|
||||||
|
setIsTransferLoading(true)
|
||||||
|
await swicCall('transferName', npub, enteredName, receiverNpub)
|
||||||
|
notify('Username transferred!', 'success')
|
||||||
|
setIsTransferLoading(false)
|
||||||
|
setEnteredName('')
|
||||||
|
} catch (error: any) {
|
||||||
|
setIsTransferLoading(false)
|
||||||
|
notify(error?.message || 'Failed to transfer username!', 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal open={isModalOpened} title="Username Settings" onClose={handleCloseModal}>
|
||||||
|
<Stack gap={'1rem'}>
|
||||||
|
<StyledSettingContainer>
|
||||||
|
<SectionTitle>Change name</SectionTitle>
|
||||||
|
<Input
|
||||||
|
label="User name"
|
||||||
|
fullWidth
|
||||||
|
placeholder="Enter a Username"
|
||||||
|
endAdornment={<Typography color={'#FFFFFFA8'}>@{DOMAIN}</Typography>}
|
||||||
|
helperText={inputHelperText}
|
||||||
|
onChange={handleNameChange}
|
||||||
|
value={enteredName}
|
||||||
|
helperTextProps={{
|
||||||
|
sx: {
|
||||||
|
'&.helper_text': {
|
||||||
|
color: getHelperTextColor(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button fullWidth disabled={isEditButtonDisabled} onClick={handleEditName}>
|
||||||
|
Save name {isLoading && <LoadingSpinner />}
|
||||||
|
</Button>
|
||||||
|
</StyledSettingContainer>
|
||||||
|
<StyledSettingContainer>
|
||||||
|
<SectionTitle>Transfer name</SectionTitle>
|
||||||
|
<Input
|
||||||
|
label="Receiver npub"
|
||||||
|
fullWidth
|
||||||
|
placeholder="npub1..."
|
||||||
|
onChange={handleReceiverNpubChange}
|
||||||
|
value={receiverNpub}
|
||||||
|
/>
|
||||||
|
<Button fullWidth onClick={handleTransferName} disabled={isTransferButtonDisabled}>
|
||||||
|
Transfer name
|
||||||
|
</Button>
|
||||||
|
</StyledSettingContainer>
|
||||||
|
</Stack>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
10
src/components/Modal/ModalEditName/styled.tsx
Normal file
10
src/components/Modal/ModalEditName/styled.tsx
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Stack, StackProps, styled } from "@mui/material";
|
||||||
|
|
||||||
|
export const StyledSettingContainer = styled((props: StackProps) => (
|
||||||
|
<Stack gap={'0.75rem'} component={'form'} {...props} />
|
||||||
|
))(({ theme }) => ({
|
||||||
|
padding: '1rem',
|
||||||
|
borderRadius: '1rem',
|
||||||
|
background: theme.palette.background.default,
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
}))
|
@@ -60,7 +60,10 @@ export const ModalSignUp = () => {
|
|||||||
try {
|
try {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const k: any = await swicCall('generateKey', name)
|
const k: any = await swicCall('generateKey', name)
|
||||||
notify(`Account created for "${name}"`, 'success')
|
if (k.name)
|
||||||
|
notify(`Account created for "${k.name}"`, 'success')
|
||||||
|
else
|
||||||
|
notify(`Failed to assign name "${name}", try again`, 'error')
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// give frontend time to read the new key first
|
// give frontend time to read the new key first
|
||||||
|
@@ -28,6 +28,7 @@ enum DECISION {
|
|||||||
export interface KeyInfo {
|
export interface KeyInfo {
|
||||||
npub: string
|
npub: string
|
||||||
nip05?: string
|
nip05?: string
|
||||||
|
name?: string
|
||||||
locked: boolean
|
locked: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,13 +246,8 @@ export class NoauthBackend {
|
|||||||
|
|
||||||
const self = this
|
const self = this
|
||||||
swg.addEventListener('activate', (event) => {
|
swg.addEventListener('activate', (event) => {
|
||||||
console.log('activate')
|
console.log('activate new sw worker')
|
||||||
// swg.addEventListener('activate', event => event.waitUntil(swg.clients.claim()));
|
this.reloadUI()
|
||||||
})
|
|
||||||
|
|
||||||
swg.addEventListener('install', (event) => {
|
|
||||||
console.log('install')
|
|
||||||
// swg.addEventListener('install', event => event.waitUntil(swg.skipWaiting()));
|
|
||||||
})
|
})
|
||||||
|
|
||||||
swg.addEventListener('push', (event) => {
|
swg.addEventListener('push', (event) => {
|
||||||
@@ -366,7 +362,7 @@ export class NoauthBackend {
|
|||||||
if (r.status !== 200 && r.status !== 201) {
|
if (r.status !== 200 && r.status !== 201) {
|
||||||
console.log('Fetch error', url, method, r.status)
|
console.log('Fetch error', url, method, r.status)
|
||||||
const body = await r.json()
|
const body = await r.json()
|
||||||
throw new Error('Failed to fetch ' + url, { cause: body })
|
throw new Error('Failed to fetch ' + url, { cause: { body, status: r.status } })
|
||||||
}
|
}
|
||||||
|
|
||||||
return await r.json()
|
return await r.json()
|
||||||
@@ -501,13 +497,48 @@ export class NoauthBackend {
|
|||||||
})
|
})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log('error', e.cause)
|
console.log('error', e.cause)
|
||||||
if (e.cause && e.cause.minPow > pow) pow = e.cause.minPow
|
if (e.cause && e.cause.body && e.cause.body.minPow > pow) pow = e.cause.body.minPow
|
||||||
else throw e
|
else throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error('Too many requests, retry later')
|
throw new Error('Too many requests, retry later')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async sendDeleteNameToServer(npub: string, name: string) {
|
||||||
|
const body = JSON.stringify({
|
||||||
|
npub,
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
|
||||||
|
const method = 'DELETE'
|
||||||
|
const url = `${NOAUTHD_URL}/name`
|
||||||
|
|
||||||
|
return this.sendPostAuthd({
|
||||||
|
npub,
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendTransferNameToServer(npub: string, name: string, newNpub: string) {
|
||||||
|
const body = JSON.stringify({
|
||||||
|
npub,
|
||||||
|
name,
|
||||||
|
newNpub,
|
||||||
|
})
|
||||||
|
|
||||||
|
const method = 'PUT'
|
||||||
|
const url = `${NOAUTHD_URL}/name`
|
||||||
|
|
||||||
|
return this.sendPostAuthd({
|
||||||
|
npub,
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private async sendTokenToServer(npub: string, token: string) {
|
private async sendTokenToServer(npub: string, token: string) {
|
||||||
const body = JSON.stringify({
|
const body = JSON.stringify({
|
||||||
npub,
|
npub,
|
||||||
@@ -602,6 +633,7 @@ export class NoauthBackend {
|
|||||||
return {
|
return {
|
||||||
npub: k.npub,
|
npub: k.npub,
|
||||||
nip05: k.nip05,
|
nip05: k.nip05,
|
||||||
|
name: k.name,
|
||||||
locked: this.isLocked(k.npub),
|
locked: this.isLocked(k.npub),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -642,11 +674,16 @@ export class NoauthBackend {
|
|||||||
await this.startKey({ npub, sk })
|
await this.startKey({ npub, sk })
|
||||||
|
|
||||||
// assign nip05 before adding the key
|
// assign nip05 before adding the key
|
||||||
// FIXME set name to db and if this call to 'send' fails
|
|
||||||
// then retry later
|
|
||||||
if (!existingName && name && !name.includes('@')) {
|
if (!existingName && name && !name.includes('@')) {
|
||||||
console.log('adding key', npub, name)
|
console.log('adding key', npub, name)
|
||||||
|
try {
|
||||||
await this.sendNameToServer(npub, name)
|
await this.sendNameToServer(npub, name)
|
||||||
|
} catch (e) {
|
||||||
|
console.log('create name failed', e)
|
||||||
|
// clear it
|
||||||
|
await dbi.editName(npub, '')
|
||||||
|
dbKey.name = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const sub = await this.swg.registration.pushManager.getSubscription()
|
const sub = await this.swg.registration.pushManager.getSubscription()
|
||||||
@@ -1116,6 +1153,36 @@ export class NoauthBackend {
|
|||||||
this.updateUI()
|
this.updateUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async editName(npub: string, name: string) {
|
||||||
|
const key = this.enckeys.find((k) => k.npub == npub)
|
||||||
|
if (!key) throw new Error('Npub not found')
|
||||||
|
if (key.name) {
|
||||||
|
try {
|
||||||
|
await this.sendDeleteNameToServer(npub, key.name)
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.cause && e.cause.status !== 404) throw e
|
||||||
|
console.log("Deleted name didn't exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (name) {
|
||||||
|
await this.sendNameToServer(npub, name)
|
||||||
|
}
|
||||||
|
await dbi.editName(npub, name)
|
||||||
|
key.name = name
|
||||||
|
this.updateUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
private async transferName(npub: string, name: string, newNpub: string) {
|
||||||
|
const key = this.enckeys.find(k => k.npub === npub)
|
||||||
|
if (!key) throw new Error("Npub not found")
|
||||||
|
if (!name) throw new Error("Empty name")
|
||||||
|
if (key.name !== name) throw new Error("Name changed, please reload")
|
||||||
|
await this.sendTransferNameToServer(npub, key.name, newNpub)
|
||||||
|
await dbi.editName(npub, '')
|
||||||
|
key.name = ''
|
||||||
|
this.updateUI()
|
||||||
|
}
|
||||||
|
|
||||||
private async enablePush(): Promise<boolean> {
|
private async enablePush(): Promise<boolean> {
|
||||||
const options = {
|
const options = {
|
||||||
userVisibleOnly: true,
|
userVisibleOnly: true,
|
||||||
@@ -1162,6 +1229,10 @@ export class NoauthBackend {
|
|||||||
result = await this.deleteApp(args[0])
|
result = await this.deleteApp(args[0])
|
||||||
} else if (method === 'deletePerm') {
|
} else if (method === 'deletePerm') {
|
||||||
result = await this.deletePerm(args[0])
|
result = await this.deletePerm(args[0])
|
||||||
|
} else if (method === 'editName') {
|
||||||
|
result = await this.editName(args[0], args[1])
|
||||||
|
} else if (method === 'transferName') {
|
||||||
|
result = await this.transferName(args[0], args[1], args[2])
|
||||||
} else if (method === 'enablePush') {
|
} else if (method === 'enablePush') {
|
||||||
result = await this.enablePush()
|
result = await this.enablePush()
|
||||||
} else if (method === 'fetchPendingRequests') {
|
} else if (method === 'fetchPendingRequests') {
|
||||||
@@ -1192,10 +1263,20 @@ export class NoauthBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async reloadUI() {
|
||||||
|
const clients = await this.swg.clients.matchAll({
|
||||||
|
includeUncontrolled: true,
|
||||||
|
})
|
||||||
|
console.log('reloadUI clients', clients.length)
|
||||||
|
for (const client of clients) {
|
||||||
|
client.postMessage({ result: 'reload' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async onPush(event: any) {
|
public async onPush(event: any) {
|
||||||
console.log('push', { data: event.data })
|
console.log('push', { data: event.data })
|
||||||
// noop - we just need browser to launch this worker
|
// noop - we just need browser to launch this worker
|
||||||
// FIXME use event.waitUntil and and unblock after we
|
// FIXME use event.waitUntil and and unblock after we
|
||||||
// show a notification
|
// show a notification to avoid annoying the browser
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -89,6 +89,16 @@ export const dbi = {
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
editName: async (npub: string, name: string): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await db.keys.where({ npub }).modify({
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`db editName error: ${error}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
getApp: async (appNpub: string) => {
|
getApp: async (appNpub: string) => {
|
||||||
try {
|
try {
|
||||||
return await db.apps.get(appNpub)
|
return await db.apps.get(appNpub)
|
||||||
|
@@ -1,10 +1,12 @@
|
|||||||
// service-worker client interface
|
// service-worker client interface,
|
||||||
|
// works on the frontend, not sw
|
||||||
import * as serviceWorkerRegistration from '../serviceWorkerRegistration'
|
import * as serviceWorkerRegistration from '../serviceWorkerRegistration'
|
||||||
|
|
||||||
export let swr: ServiceWorkerRegistration | null = null
|
export let swr: ServiceWorkerRegistration | null = null
|
||||||
const reqs = new Map<number, { ok: (r: any) => void; rej: (r: any) => void }>()
|
const reqs = new Map<number, { ok: (r: any) => void; rej: (r: any) => void }>()
|
||||||
let nextReqId = 1
|
let nextReqId = 1
|
||||||
let onRender: (() => void) | null = null
|
let onRender: (() => void) | null = null
|
||||||
|
let onReload: (() => void) | null = null
|
||||||
const queue: (() => Promise<void> | void)[] = []
|
const queue: (() => Promise<void> | void)[] = []
|
||||||
|
|
||||||
export async function swicRegister() {
|
export async function swicRegister() {
|
||||||
@@ -14,8 +16,12 @@ export async function swicRegister() {
|
|||||||
swr = registration
|
swr = registration
|
||||||
},
|
},
|
||||||
onError(e) {
|
onError(e) {
|
||||||
console.log(`error ${e}`)
|
console.log('sw error', e)
|
||||||
},
|
},
|
||||||
|
onUpdate() {
|
||||||
|
// tell new SW that it should activate immediately
|
||||||
|
swr?.waiting?.postMessage({type: 'SKIP_WAITING'})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
navigator.serviceWorker.ready.then(async (r) => {
|
navigator.serviceWorker.ready.then(async (r) => {
|
||||||
@@ -48,7 +54,11 @@ function onMessage(data: any) {
|
|||||||
console.log('SW message', id, result, error)
|
console.log('SW message', id, result, error)
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
if (result === 'reload') {
|
||||||
|
if (onReload) onReload()
|
||||||
|
} else {
|
||||||
if (onRender) onRender()
|
if (onRender) onRender()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,3 +103,7 @@ export async function swicCall(method: string, ...args: any[]) {
|
|||||||
export function swicOnRender(cb: () => void) {
|
export function swicOnRender(cb: () => void) {
|
||||||
onRender = cb
|
onRender = cb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function swicOnReload(cb: () => void) {
|
||||||
|
onReload = cb
|
||||||
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
|
import { useCallback, useState } from 'react'
|
||||||
import { useAppSelector } from '../../store/hooks/redux'
|
import { useAppSelector } from '../../store/hooks/redux'
|
||||||
import { Navigate, useParams, useSearchParams } from 'react-router-dom'
|
import { Navigate, useParams, useSearchParams } from 'react-router-dom'
|
||||||
import { Stack } from '@mui/material'
|
import { Box, IconButton, Stack } from '@mui/material'
|
||||||
import { StyledIconButton } from './styled'
|
import { StyledIconButton } from './styled'
|
||||||
import { SettingsIcon, ShareIcon } from '@/assets'
|
import { SettingsIcon, ShareIcon } from '@/assets'
|
||||||
import { Apps } from './components/Apps'
|
import { Apps } from './components/Apps'
|
||||||
@@ -18,7 +19,9 @@ import { useTriggerConfirmModal } from './hooks/useTriggerConfirmModal'
|
|||||||
import { useLiveQuery } from 'dexie-react-hooks'
|
import { useLiveQuery } from 'dexie-react-hooks'
|
||||||
import { checkNpubSyncQuerier } from './utils'
|
import { checkNpubSyncQuerier } from './utils'
|
||||||
import { DOMAIN } from '@/utils/consts'
|
import { DOMAIN } from '@/utils/consts'
|
||||||
import { useCallback, useState } from 'react'
|
import { InputCopyButton } from '@/shared/InputCopyButton/InputCopyButton'
|
||||||
|
import MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded'
|
||||||
|
import { ModalEditName } from '@/components/Modal/ModalEditName/ModalEditName'
|
||||||
|
|
||||||
const KeyPage = () => {
|
const KeyPage = () => {
|
||||||
const { npub = '' } = useParams<{ npub: string }>()
|
const { npub = '' } = useParams<{ npub: string }>()
|
||||||
@@ -60,6 +63,7 @@ const KeyPage = () => {
|
|||||||
|
|
||||||
const handleOpenConnectAppModal = () => handleOpen(MODAL_PARAMS_KEYS.CONNECT_APP)
|
const handleOpenConnectAppModal = () => handleOpen(MODAL_PARAMS_KEYS.CONNECT_APP)
|
||||||
const handleOpenSettingsModal = () => handleOpen(MODAL_PARAMS_KEYS.SETTINGS)
|
const handleOpenSettingsModal = () => handleOpen(MODAL_PARAMS_KEYS.SETTINGS)
|
||||||
|
const handleOpenEditNameModal = () => handleOpen(MODAL_PARAMS_KEYS.EDIT_NAME)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -70,13 +74,20 @@ const KeyPage = () => {
|
|||||||
<UserValueSection
|
<UserValueSection
|
||||||
title="Your login"
|
title="Your login"
|
||||||
value={username}
|
value={username}
|
||||||
copyValue={username}
|
endAdornment={
|
||||||
|
<Box display={'flex'} alignItems={'center'} gap={'0.25rem'}>
|
||||||
|
<IconButton onClick={handleOpenEditNameModal} color={username ? 'default' : 'error'}>
|
||||||
|
<MoreHorizRoundedIcon />
|
||||||
|
</IconButton>
|
||||||
|
<InputCopyButton value={username} />
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
explanationType={EXPLANATION_MODAL_KEYS.LOGIN}
|
explanationType={EXPLANATION_MODAL_KEYS.LOGIN}
|
||||||
/>
|
/>
|
||||||
<UserValueSection
|
<UserValueSection
|
||||||
title="Your NPUB"
|
title="Your NPUB"
|
||||||
value={npub}
|
value={npub}
|
||||||
copyValue={npub}
|
endAdornment={<InputCopyButton value={npub} />}
|
||||||
explanationType={EXPLANATION_MODAL_KEYS.NPUB}
|
explanationType={EXPLANATION_MODAL_KEYS.NPUB}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -98,11 +109,13 @@ const KeyPage = () => {
|
|||||||
|
|
||||||
<Apps apps={filteredApps} npub={npub} />
|
<Apps apps={filteredApps} npub={npub} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<ModalConnectApp />
|
<ModalConnectApp />
|
||||||
<ModalSettings isSynced={isSynced} />
|
<ModalSettings isSynced={isSynced} />
|
||||||
<ModalExplanation />
|
<ModalExplanation />
|
||||||
<ModalConfirmConnect />
|
<ModalConfirmConnect />
|
||||||
<ModalConfirmEvent confirmEventReqs={prepareEventPendings} />
|
<ModalConfirmEvent confirmEventReqs={prepareEventPendings} />
|
||||||
|
<ModalEditName />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,6 @@ import { Box, Stack } from '@mui/material'
|
|||||||
import { EXPLANATION_MODAL_KEYS, MODAL_PARAMS_KEYS } from '@/types/modal'
|
import { EXPLANATION_MODAL_KEYS, MODAL_PARAMS_KEYS } from '@/types/modal'
|
||||||
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
|
import { SectionTitle } from '@/shared/SectionTitle/SectionTitle'
|
||||||
import { AppLink } from '@/shared/AppLink/AppLink'
|
import { AppLink } from '@/shared/AppLink/AppLink'
|
||||||
import { InputCopyButton } from '@/shared/InputCopyButton/InputCopyButton'
|
|
||||||
import { StyledInput } from '../styled'
|
import { StyledInput } from '../styled'
|
||||||
import { useModalSearchParams } from '@/hooks/useModalSearchParams'
|
import { useModalSearchParams } from '@/hooks/useModalSearchParams'
|
||||||
|
|
||||||
@@ -11,10 +10,10 @@ type UserValueSectionProps = {
|
|||||||
title: string
|
title: string
|
||||||
value: string
|
value: string
|
||||||
explanationType: EXPLANATION_MODAL_KEYS
|
explanationType: EXPLANATION_MODAL_KEYS
|
||||||
copyValue: string
|
endAdornment?: React.ReactNode
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserValueSection: FC<UserValueSectionProps> = ({ title, value, explanationType, copyValue }) => {
|
const UserValueSection: FC<UserValueSectionProps> = ({ title, value, explanationType, endAdornment }) => {
|
||||||
const { handleOpen } = useModalSearchParams()
|
const { handleOpen } = useModalSearchParams()
|
||||||
|
|
||||||
const handleOpenExplanationModal = (type: EXPLANATION_MODAL_KEYS) => {
|
const handleOpenExplanationModal = (type: EXPLANATION_MODAL_KEYS) => {
|
||||||
@@ -30,7 +29,7 @@ const UserValueSection: FC<UserValueSectionProps> = ({ title, value, explanation
|
|||||||
<SectionTitle>{title}</SectionTitle>
|
<SectionTitle>{title}</SectionTitle>
|
||||||
<AppLink title="What is this?" onClick={() => handleOpenExplanationModal(explanationType)} />
|
<AppLink title="What is this?" onClick={() => handleOpenExplanationModal(explanationType)} />
|
||||||
</Stack>
|
</Stack>
|
||||||
<StyledInput value={value} readOnly endAdornment={<InputCopyButton value={copyValue} />} />
|
<StyledInput value={value} readOnly endAdornment={endAdornment} />
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -39,9 +39,9 @@ const StyledButton = styled(
|
|||||||
background: theme.palette.primary.main,
|
background: theme.palette.primary.main,
|
||||||
},
|
},
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
'&.disabled': {
|
'&.button.disabled': {
|
||||||
color: theme.palette.text.secondary,
|
color: theme.palette.text.secondary,
|
||||||
background: `${theme.palette.primary.main}50`,
|
background: `${theme.palette.primary.main}75`,
|
||||||
cursor: 'not-allowed',
|
cursor: 'not-allowed',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,12 @@ export const Input = forwardRef<HTMLInputElement, AppInputProps>(
|
|||||||
{label}
|
{label}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
) : null}
|
) : null}
|
||||||
<InputBase autoComplete="off" className="input" {...props} classes={{ error: 'error' }} ref={ref} />
|
<InputBase
|
||||||
|
autoComplete="off"
|
||||||
|
{...props}
|
||||||
|
classes={{ error: 'error', root: 'input_root', input: 'input', disabled: 'disabled' }}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
{helperText ? (
|
{helperText ? (
|
||||||
<FormHelperText {...helperTextProps} className="helper_text">
|
<FormHelperText {...helperTextProps} className="helper_text">
|
||||||
{helperText}
|
{helperText}
|
||||||
@@ -41,20 +46,20 @@ const StyledInputContainer = styled((props: BoxProps) => <Box {...props} />)(({
|
|||||||
const isDark = theme.palette.mode === 'dark'
|
const isDark = theme.palette.mode === 'dark'
|
||||||
return {
|
return {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
'& > .input': {
|
'& > .input_root': {
|
||||||
background: isDark ? '#000000A8' : '#000',
|
background: isDark ? '#000000A8' : '#000',
|
||||||
color: theme.palette.common.white,
|
color: theme.palette.common.white,
|
||||||
padding: '0.75rem 1rem',
|
padding: '0.75rem 1rem',
|
||||||
borderRadius: '1rem',
|
borderRadius: '1rem',
|
||||||
border: '0.3px solid #FFFFFF54',
|
border: '0.3px solid #FFFFFF54',
|
||||||
fontSize: '0.875rem',
|
fontSize: '0.875rem',
|
||||||
'& input::placeholder': {
|
|
||||||
color: '#fff',
|
|
||||||
},
|
|
||||||
'&.error': {
|
'&.error': {
|
||||||
border: '0.3px solid ' + theme.palette.error.main,
|
border: '0.3px solid ' + theme.palette.error.main,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
'& .input:is(.disabled, &)': {
|
||||||
|
WebkitTextFillColor: '#ffffff80',
|
||||||
|
},
|
||||||
'& > .helper_text': {
|
'& > .helper_text': {
|
||||||
margin: '0.5rem 0.5rem 0',
|
margin: '0.5rem 0.5rem 0',
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
|
@@ -10,6 +10,7 @@ export enum MODAL_PARAMS_KEYS {
|
|||||||
CONFIRM_EVENT = 'confirm-event',
|
CONFIRM_EVENT = 'confirm-event',
|
||||||
ACTIVITY = 'activity',
|
ACTIVITY = 'activity',
|
||||||
APP_DETAILS = 'app-details',
|
APP_DETAILS = 'app-details',
|
||||||
|
EDIT_NAME = 'edit-name',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EXPLANATION_MODAL_KEYS {
|
export enum EXPLANATION_MODAL_KEYS {
|
||||||
|
Reference in New Issue
Block a user