Compare commits

...

27 Commits

Author SHA1 Message Date
Nostr.Band
9b10952749
Merge pull request #118 from nostrband/develop
Change login button name to Login
2024-02-23 08:29:57 +03:00
artur
6a6b18bcad Change login button name to Login 2024-02-23 08:29:12 +03:00
Nostr.Band
67787c182b
Merge pull request #116 from nostrband/develop
CSS improvements, make nip05 take priority over profile.name
2024-02-22 14:26:35 +03:00
artur
8c0b5f379e Make nip05 name take priority over profile.name 2024-02-22 14:21:24 +03:00
Nostr.Band
905dc7ac1b
Merge pull request #112 from nostrband/feature/adaptive-styles
Feature/adaptive styles
2024-02-22 13:43:27 +03:00
Nostr.Band
5259e17447
Merge pull request #113 from nostrband/develop
Drop old pending requests
2024-02-22 13:27:01 +03:00
artur
ca25712d20 Drop old pending requests 2024-02-22 13:07:56 +03:00
Bekbolsun
6e334c5078 fix adaptive styles 2024-02-21 20:51:07 +06:00
Nostr.Band
4d7b2c5a69
Merge pull request #110 from nostrband/develop
Add 27235 to basic perms
2024-02-21 14:11:57 +03:00
artur
051eaf001f Add 10000 kind to basic perms 2024-02-21 14:10:57 +03:00
artur
4a2362f6b9 Add 27235 kind to basic perms 2024-02-21 14:09:47 +03:00
Bekbolsun
da6f68e00a Merge branch 'develop' of https://github.com/nostrband/noauth into feature/adaptive-styles 2024-02-20 18:33:50 +06:00
Bekbolsun
c08e26629a add adaptive styles in modal connect 2024-02-20 18:33:36 +06:00
Nostr.Band
3de39c35b0
Merge pull request #106 from nostrband/develop
Reload button, local fonts
2024-02-20 11:19:16 +03:00
Nostr.Band
7aba51b103
Merge pull request #104 from nostrband/refactor/reload-badge
refactor reload badge showing logic
2024-02-20 11:13:27 +03:00
Nostr.Band
c61869b2a2
Merge pull request #105 from nostrband/feature/local-fonts
Feature/local fonts
2024-02-20 11:12:02 +03:00
Bekbolsun
47dc8e20fe add local fonts 2024-02-20 10:39:46 +06:00
Nostr.Band
98345037aa
Merge pull request #103 from nostrband/develop
Add 'reload' banner on update
2024-02-19 19:43:25 +03:00
Nostr.Band
3e86ad37b9
Merge pull request #102 from nostrband/develop
Remove disallow/remember on popup close, add pause before sending aut…
2024-02-19 19:32:58 +03:00
Nostr.Band
febc91632a
Merge pull request #101 from nostrband/develop
Fix remove auto-close in popup if req not exists
2024-02-19 18:48:27 +03:00
Bekbolsun
7379d75002 trying add fonts locally 2024-02-19 19:31:14 +06:00
Nostr.Band
71accbf983
Merge pull request #99 from nostrband/develop
Fix ignore unknown methods
2024-02-19 14:56:29 +03:00
Nostr.Band
dddc90308a
Merge pull request #97 from nostrband/feature/sw-update
Feature/sw update
2024-02-19 14:13:57 +03:00
Nostr.Band
648567cac8
Merge pull request #96 from nostrband/develop
Add name edit/transfer, add signup/login hints
2024-02-19 11:32:31 +03:00
Nostr.Band
f0a7f5c58a
Merge pull request #88 from nostrband/develop
Show app npub if app only has url
2024-02-16 15:34:57 +03:00
Nostr.Band
64082de238
Merge pull request #87 from nostrband/develop
Don't die on enable push failure
2024-02-16 15:09:05 +03:00
Nostr.Band
170daa9ee7
Merge pull request #85 from nostrband/develop
Ignore + watcher logic
2024-02-16 14:49:21 +03:00
49 changed files with 526 additions and 196 deletions

View File

@ -3,37 +3,37 @@ const path = require('path')
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin') const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin')
module.exports = function override(config) { module.exports = function override(config) {
const fallback = config.resolve.fallback || {} const fallback = config.resolve.fallback || {}
Object.assign(fallback, { Object.assign(fallback, {
crypto: require.resolve('crypto-browserify'), crypto: require.resolve('crypto-browserify'),
stream: require.resolve('stream-browserify'), stream: require.resolve('stream-browserify'),
assert: require.resolve('assert'), assert: require.resolve('assert'),
http: require.resolve('stream-http'), http: require.resolve('stream-http'),
https: require.resolve('https-browserify'), https: require.resolve('https-browserify'),
os: require.resolve('os-browserify'), os: require.resolve('os-browserify'),
url: require.resolve('url'), url: require.resolve('url'),
}) })
config.resolve.fallback = fallback config.resolve.fallback = fallback
config.plugins = (config.plugins || []).concat([ config.plugins = (config.plugins || []).concat([
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
process: 'process/browser', process: 'process/browser',
Buffer: ['buffer', 'Buffer'], Buffer: ['buffer', 'Buffer'],
}), }),
]) ])
config.module.rules.unshift({ config.module.rules.unshift({
test: /\.m?js$/, test: /\.m?js$/,
resolve: { resolve: {
fullySpecified: false, // disable the behavior fullySpecified: false, // disable the behavior
}, },
}) })
// turns off the plugin that forbids importing from node_modules for the above-mentioned stuff // turns off the plugin that forbids importing from node_modules for the above-mentioned stuff
config.resolve.plugins = config.resolve.plugins.filter((plugin) => { config.resolve.plugins = config.resolve.plugins.filter((plugin) => {
return !(plugin instanceof ModuleScopePlugin) return !(plugin instanceof ModuleScopePlugin)
}) })
config.resolve.alias = { config.resolve.alias = {
'@': path.resolve(__dirname, 'src'), '@': path.resolve(__dirname, 'src'),
} }
return config return config
} }

View File

@ -10,12 +10,6 @@
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png" /> <link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png" /> <link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700&display=swap"
rel="stylesheet"
/>
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,4 +1,5 @@
import { AppInputProps, Input } from '@/shared/Input/Input' import { Input } from '@/shared/Input/Input'
import { AppInputProps } from '@/shared/Input/types'
import { styled } from '@mui/material' import { styled } from '@mui/material'
import { forwardRef } from 'react' import { forwardRef } from 'react'

View File

@ -97,8 +97,7 @@ export const ModalConfirmConnect = () => {
if (isModalOpened && (!isNpubExists /*|| !isAppNpubExists*/ || (pendingReqId && !isPendingReqIdExists))) { if (isModalOpened && (!isNpubExists /*|| !isAppNpubExists*/ || (pendingReqId && !isPendingReqIdExists))) {
// if (isPopup) window.close() // if (isPopup) window.close()
// else closeModalAfterRequest() // else closeModalAfterRequest()
if (!isPopup) if (!isPopup) closeModalAfterRequest()
closeModalAfterRequest()
return null return null
} }
} }
@ -198,11 +197,11 @@ export const ModalConfirmConnect = () => {
> >
{appAvatarTitle} {appAvatarTitle}
</Avatar> </Avatar>
<Box> <Box overflow={'auto'}>
<Typography variant="h5" fontWeight={600}> <Typography variant="h5" fontWeight={600} noWrap>
{appName} {appName}
</Typography> </Typography>
<Typography variant="body2" color={'GrayText'}> <Typography variant="body2" color={'GrayText'} noWrap>
New app would like to connect New app would like to connect
</Typography> </Typography>
</Box> </Box>

View File

@ -21,5 +21,8 @@ export const StyledToggleButtonsGroup = styled((props: ToggleButtonGroupProps) =
border: 'initial', border: 'initial',
borderRadius: '1rem', borderRadius: '1rem',
}, },
'@media screen and (max-width: 320px)': {
marginBottom: '0.25rem',
},
}) })
) )

View File

@ -4,22 +4,18 @@ import { StyledToggleButton } from './styled'
type ActionToggleButtonProps = ToggleButtonProps & { type ActionToggleButtonProps = ToggleButtonProps & {
description?: string description?: string
hasinfo?: boolean
} }
export const ActionToggleButton: FC<ActionToggleButtonProps> = ({ hasinfo = false, ...props }) => { export const ActionToggleButton: FC<ActionToggleButtonProps> = (props) => {
const { title, description = '' } = props const { title, description = '' } = props
return ( return (
<StyledToggleButton {...props}> <StyledToggleButton {...props}>
<Typography variant="body2">{title}</Typography> <Typography variant="body2" noWrap className="title">
{title}
</Typography>
<Typography className="description" variant="caption" color={'GrayText'}> <Typography className="description" variant="caption" color={'GrayText'}>
{description} {description}
</Typography> </Typography>
{hasinfo && (
<Typography className="info" color={'GrayText'}>
Info
</Typography>
)}
</StyledToggleButton> </StyledToggleButton>
) )
} }

View File

@ -29,4 +29,17 @@ export const StyledToggleButton = styled((props: ToggleButtonProps) => (
fontSize: '10px', fontSize: '10px',
fontWeight: 500, fontWeight: 500,
}, },
'@media screen and (max-width: 320px)': {
'& .title': {
fontSize: '14px',
},
'& .description': {
margin: '0.25rem 0',
display: '-webkit-box',
WebkitLineClamp: 3,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis',
},
},
})) }))

View File

@ -18,6 +18,7 @@ import { DOMAIN } from '@/utils/consts'
import { CheckmarkIcon } from '@/assets' import { CheckmarkIcon } from '@/assets'
import { getPublicKey, nip19 } from 'nostr-tools' import { getPublicKey, nip19 } from 'nostr-tools'
import { LoadingSpinner } from '@/shared/LoadingSpinner/LoadingSpinner' import { LoadingSpinner } from '@/shared/LoadingSpinner/LoadingSpinner'
import { HeadingContainer } from './styled'
const FORM_DEFAULT_VALUES = { const FORM_DEFAULT_VALUES = {
username: '', username: '',
@ -150,14 +151,14 @@ export const ModalImportKeys = () => {
return ( return (
<Modal open={isModalOpened} onClose={handleCloseModal} withCloseButton={false}> <Modal open={isModalOpened} onClose={handleCloseModal} withCloseButton={false}>
<Stack paddingTop={'1rem'} gap={'1rem'} component={'form'} onSubmit={handleSubmit(submitHandler)}> <Stack paddingTop={'1rem'} gap={'1rem'} component={'form'} onSubmit={handleSubmit(submitHandler)}>
<Stack gap={'0.2rem'} padding={'0 1rem'} alignSelf={'flex-start'}> <HeadingContainer>
<Typography fontWeight={600} variant="h5"> <Typography fontWeight={600} variant="h5">
Import key Import key
</Typography> </Typography>
<Typography noWrap variant="body2" color={'GrayText'}> <Typography noWrap variant="body2" color={'GrayText'}>
Bring your existing Nostr keys to Nsec.app Bring your existing Nostr keys to Nsec.app
</Typography> </Typography>
</Stack> </HeadingContainer>
<Input <Input
label="Choose a username" label="Choose a username"
fullWidth fullWidth

View File

@ -1,5 +1,5 @@
import { AppLogo } from '@/assets' import { AppLogo } from '@/assets'
import { Box, styled } from '@mui/material' import { Box, Stack, StackProps, styled } from '@mui/material'
export const StyledAppLogo = styled((props) => ( export const StyledAppLogo = styled((props) => (
<Box {...props}> <Box {...props}>
@ -12,3 +12,14 @@ export const StyledAppLogo = styled((props) => (
display: 'grid', display: 'grid',
placeItems: 'center', placeItems: 'center',
})) }))
export const HeadingContainer = styled((props: StackProps) => <Stack {...props} />)(() => ({
gap: '0.2rem',
padding: '0 1rem',
alignSelf: 'flex-start',
overflow: 'auto',
width: '100%',
'@media screen and (max-width: 320px)': {
padding: '0 0.75rem',
},
}))

View File

@ -147,7 +147,7 @@ export const ModalLogin = () => {
<Stack gap={'0.5rem'}> <Stack gap={'0.5rem'}>
<Button type="submit" fullWidth disabled={isLoading}> <Button type="submit" fullWidth disabled={isLoading}>
Add account {isLoading && <LoadingSpinner />} Login {isLoading && <LoadingSpinner />}
</Button> </Button>
</Stack> </Stack>
</Stack> </Stack>

View File

@ -1,5 +1,5 @@
import { FC, memo, useCallback } from 'react' import { FC, memo, useCallback } from 'react'
import { Collapse, Stack, Typography } from '@mui/material' import { Stack, Typography } from '@mui/material'
import { StyledAlert, StyledReloadButton } from './styled' import { StyledAlert, StyledReloadButton } from './styled'
import { useSessionStorage } from 'usehooks-ts' import { useSessionStorage } from 'usehooks-ts'
import { RELOAD_STORAGE_KEY } from '@/utils/consts' import { RELOAD_STORAGE_KEY } from '@/utils/consts'
@ -10,16 +10,14 @@ type ReloadBadgeContentProps = {
const ReloadBadgeContent: FC<ReloadBadgeContentProps> = memo(({ onReload }) => { const ReloadBadgeContent: FC<ReloadBadgeContentProps> = memo(({ onReload }) => {
return ( return (
<Collapse in> <StyledAlert>
<StyledAlert> <Stack direction={'row'} className="content">
<Stack direction={'row'} className="content"> <Typography flex={1} className="title">
<Typography flex={1} className="title"> New version available!
New version available! </Typography>
</Typography> <StyledReloadButton onClick={onReload}>Reload</StyledReloadButton>
<StyledReloadButton onClick={onReload}>Reload</StyledReloadButton> </Stack>
</Stack> </StyledAlert>
</StyledAlert>
</Collapse>
) )
}) })

View File

@ -1,21 +1,27 @@
import { FC, ReactNode } from 'react' import { FC, ReactNode } from 'react'
import { IconContainer, StyledContainer } from './styled' import { IconContainer, StyledContainer } from './styled'
import { BoxProps, Stack, Typography } from '@mui/material' import { BoxProps, Typography } from '@mui/material'
type WarningProps = { type WarningProps = {
message?: string | ReactNode message?: string | ReactNode
hint?: string | ReactNode
icon?: ReactNode icon?: ReactNode
} & BoxProps } & BoxProps
export const Warning: FC<WarningProps> = ({ hint, message, icon, ...restProps }) => { export const Warning: FC<WarningProps> = ({ message, icon, ...restProps }) => {
const renderMessage = () => {
if (typeof message === 'string') {
return (
<Typography noWrap width={'100%'}>
{message}
</Typography>
)
}
return message
}
return ( return (
<StyledContainer {...restProps}> <StyledContainer {...restProps}>
{icon && <IconContainer>{icon}</IconContainer>} {icon && <IconContainer>{icon}</IconContainer>}
<Stack flex={1} direction={'column'} gap={'0.2rem'}> {renderMessage()}
<Typography noWrap>{message}</Typography>
{hint && <Typography>{hint}</Typography>}
</Stack>
</StyledContainer> </StyledContainer>
) )
} }

View File

@ -7,13 +7,14 @@ export const StyledContainer = styled((props: BoxProps) => <Box {...props} />)((
padding: '0.5rem', padding: '0.5rem',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
gap: '1rem', gap: '0.5rem',
cursor: 'pointer', cursor: 'pointer',
} }
}) })
export const IconContainer = styled((props: BoxProps) => <Box {...props} />)(() => ({ export const IconContainer = styled((props: BoxProps) => <Box {...props} />)(() => ({
width: '40px', width: '40px',
minWidth: '40px',
height: '40px', height: '40px',
borderRadius: '50%', borderRadius: '50%',
background: 'grey', background: 'grey',

View File

@ -14,7 +14,7 @@ export const useProfile = (npub: string) => {
const [profile, setProfile] = useState<MetaEvent | null>(null) const [profile, setProfile] = useState<MetaEvent | null>(null)
const currentKey = useAppSelector((state) => selectKeyByNpub(state, npub)) const currentKey = useAppSelector((state) => selectKeyByNpub(state, npub))
const userName = getProfileUsername(profile) || currentKey?.name const userName = currentKey?.name || getProfileUsername(profile)
const userAvatar = profile?.info?.picture || '' const userAvatar = profile?.info?.picture || ''
const avatarTitle = getFirstLetter(userName) const avatarTitle = getFirstLetter(userName)

View File

@ -1,17 +1,63 @@
@font-face {
font-family: 'Inter';
src:
local('Inter ExtraLight'),
local('Inter-ExtraLight'),
url('./assets/fonts/Inter/Inter-ExtraLight.ttf') format('truetype');
font-weight: 200;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src:
local('Inter Light'),
local('Inter-Light'),
url('./assets/fonts/Inter/Inter-Light.ttf') format('truetype');
font-weight: 300;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src:
local('Inter Regular'),
local('Inter-Regular'),
url('./assets/fonts/Inter/Inter-Regular.ttf') format('truetype');
font-weight: 400;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src:
local('Inter Medium'),
local('Inter-Medium'),
url('./assets/fonts/Inter/Inter-Medium.ttf') format('truetype');
font-weight: 500;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src:
local('Inter SemiBold'),
local('Inter-SemiBold'),
url('./assets/fonts/Inter/Inter-SemiBold.ttf') format('truetype');
font-weight: 600;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src:
local('Inter Bold'),
local('Inter-Bold'),
url('./assets/fonts/Inter/Inter-Bold.ttf') format('truetype');
font-weight: 700;
font-display: swap;
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
box-sizing: border-box; box-sizing: border-box;
} font-family: 'Inter', sans-serif;
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans',
'Droid Sans', 'Helvetica Neue', sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace;
} }
html, html,

View File

@ -1,13 +1,13 @@
import React from 'react' import React from 'react'
import ReactDOM from 'react-dom/client' import ReactDOM from 'react-dom/client'
import './index.css' import ThemeProvider from './modules/theme/ThemeProvider'
import App from './App' import App from './App'
import './index.css'
import reportWebVitals from './reportWebVitals' import reportWebVitals from './reportWebVitals'
import { swicRegister } from './modules/swic' import { swicRegister } from './modules/swic'
import { BrowserRouter } from 'react-router-dom' import { BrowserRouter } from 'react-router-dom'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { persistor, store } from './store' import { persistor, store } from './store'
import ThemeProvider from './modules/theme/ThemeProvider'
import { PersistGate } from 'redux-persist/integration/react' import { PersistGate } from 'redux-persist/integration/react'
import { SnackbarProvider } from 'notistack' import { SnackbarProvider } from 'notistack'

View File

@ -27,11 +27,17 @@ const StyledContainer = styled((props: ContainerProps) => <Container maxWidth="s
flexDirection: 'column', flexDirection: 'column',
paddingBottom: '1rem', paddingBottom: '1rem',
position: 'relative', position: 'relative',
'&': { '& > main': {
flex: 1, flex: 1,
maxHeight: '100%', maxHeight: '100%',
}, },
'&:not(.reload) > main': { '&:not(.reload) > main': {
paddingTop: 'calc(66px + 1rem)', paddingTop: 'calc(66px + 1rem)',
}, },
'@media screen and (max-width: 320px)': {
marginBottom: '0.25rem',
paddingLeft: '0.75rem',
paddingBottom: '0.75rem',
paddingRight: '0.75rem',
},
}) })

View File

@ -10,7 +10,7 @@ import NDK, {
NDKSubscriptionCacheUsage, NDKSubscriptionCacheUsage,
NDKUser, NDKUser,
} from '@nostr-dev-kit/ndk' } from '@nostr-dev-kit/ndk'
import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS, MIN_POW, MAX_POW, KIND_RPC, DOMAIN } from '../utils/consts' import { NOAUTHD_URL, WEB_PUSH_PUBKEY, NIP46_RELAYS, MIN_POW, MAX_POW, KIND_RPC, DOMAIN, REQ_TTL } from '../utils/consts'
// import { Nip04 } from './nip04' // import { Nip04 } from './nip04'
import { fetchNip05, getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers' import { fetchNip05, getReqPerm, getShortenNpub, isPackagePerm } from '@/utils/helpers/helpers'
import { NostrPowEvent, minePow } from './pow' import { NostrPowEvent, minePow } from './pow'
@ -310,6 +310,13 @@ export class NoauthBackend {
this.apps = await dbi.listApps() this.apps = await dbi.listApps()
console.log('started apps', this.apps) console.log('started apps', this.apps)
// drop old pending reqs
const pending = await dbi.listPending()
for (const p of pending) {
if (p.timestamp < Date.now() - REQ_TTL)
await dbi.removePending(p.id)
}
const sub = await this.swg.registration.pushManager.getSubscription() const sub = await this.swg.registration.pushManager.getSubscription()
for (const k of this.enckeys) { for (const k of this.enckeys) {

View File

@ -21,6 +21,7 @@ const commonTheme: Theme = createTheme({
styleOverrides: { styleOverrides: {
root: { root: {
textTransform: 'initial', textTransform: 'initial',
color: 'red',
}, },
}, },
}, },
@ -96,4 +97,6 @@ const darkTheme: Theme = createTheme({
}, },
}) })
console.log(darkTheme)
export { lightTheme, darkTheme } export { lightTheme, darkTheme }

View File

@ -9,7 +9,6 @@ import { getAppIconTitle, getDomain, getShortenNpub } from '@/utils/helpers/help
import { Button } from '@/shared/Button/Button' import { Button } from '@/shared/Button/Button'
import { ACTION_TYPE } from '@/utils/consts' import { ACTION_TYPE } from '@/utils/consts'
import { Permissions } from './components/Permissions/Permissions' import { Permissions } from './components/Permissions/Permissions'
import { StyledAppIcon } from './styled'
import { useToggleConfirm } from '@/hooks/useToggleConfirm' import { useToggleConfirm } from '@/hooks/useToggleConfirm'
import { ConfirmModal } from '@/shared/ConfirmModal/ConfirmModal' import { ConfirmModal } from '@/shared/ConfirmModal/ConfirmModal'
import { swicCall } from '@/modules/swic' import { swicCall } from '@/modules/swic'
@ -20,6 +19,8 @@ import { useModalSearchParams } from '@/hooks/useModalSearchParams'
import { MODAL_PARAMS_KEYS } from '@/types/modal' import { MODAL_PARAMS_KEYS } from '@/types/modal'
import MoreIcon from '@mui/icons-material/MoreVertRounded' import MoreIcon from '@mui/icons-material/MoreVertRounded'
import { ModalAppDetails } from '@/components/Modal/ModalAppDetails/ModalAppDetails' import { ModalAppDetails } from '@/components/Modal/ModalAppDetails/ModalAppDetails'
import { IconApp } from '@/shared/IconApp/IconApp'
import { HeadingContainer, AppInfoContainer, AppNameContainer } from './styled'
const AppPage = () => { const AppPage = () => {
const keys = useAppSelector(selectKeys) const keys = useAppSelector(selectKeys)
@ -67,12 +68,13 @@ const AppPage = () => {
<> <>
<Stack maxHeight={'100%'} overflow={'auto'} alignItems={'flex-start'} height={'100%'}> <Stack maxHeight={'100%'} overflow={'auto'} alignItems={'flex-start'} height={'100%'}>
<IOSBackButton onNavigate={() => navigate(`key/${npub}`)} /> <IOSBackButton onNavigate={() => navigate(`key/${npub}`)} />
<Stack marginBottom={'1rem'} direction={'row'} gap={'1rem'} width={'100%'} alignItems={'center'}>
<StyledAppIcon src={icon}>{appAvatarTitle}</StyledAppIcon> <HeadingContainer>
<Box flex={'1'} overflow={'hidden'}> <IconApp size="big" picture={icon} alt={appAvatarTitle} />
<Stack direction={'row'} alignItems={'flex-start'} gap={'0.5rem'} marginBottom={'0.5rem'}> <Box flex={'1'} overflow={'auto'} alignSelf={'flex-start'} width={'100%'}>
<Box display={'flex'} flexDirection={'column'} flex={1}> <AppInfoContainer>
<Typography variant="h4" noWrap> <AppNameContainer>
<Typography className="app_name" variant="h4" noWrap>
{appName} {appName}
</Typography> </Typography>
{isAppNameExists && ( {isAppNameExists && (
@ -80,16 +82,19 @@ const AppPage = () => {
{shortAppNpub} {shortAppNpub}
</Typography> </Typography>
)} )}
</Box> </AppNameContainer>
<IconButton onClick={handleShowAppDetailsModal}> <IconButton onClick={handleShowAppDetailsModal}>
<MoreIcon /> <MoreIcon />
</IconButton> </IconButton>
</Stack> </AppInfoContainer>
<Typography variant="body2" noWrap> <Typography variant="body2" noWrap>
{connectedOn} {connectedOn}
</Typography> </Typography>
</Box> </Box>
</Stack> </HeadingContainer>
<Box marginBottom={'1rem'}> <Box marginBottom={'1rem'}>
<SectionTitle marginBottom={'0.5rem'}>Disconnect</SectionTitle> <SectionTitle marginBottom={'0.5rem'}>Disconnect</SectionTitle>
<Button fullWidth onClick={handleShow}> <Button fullWidth onClick={handleShow}>

View File

@ -1,6 +1,32 @@
import { Avatar, AvatarProps, styled } from '@mui/material' import { Box, BoxProps, Stack, StackProps, styled } from '@mui/material'
export const StyledAppIcon = styled((props: AvatarProps) => <Avatar {...props} variant="rounded" />)(() => ({ export const HeadingContainer = styled((props: StackProps) => <Stack {...props} />)(() => ({
width: 70, width: '100%',
height: 70, marginBottom: '1rem',
flexDirection: 'row',
gap: '1rem',
alignItems: 'center',
'@media screen and (max-width: 320px)': {
flexDirection: 'column',
gap: '0.5rem',
},
}))
export const AppInfoContainer = styled((props: StackProps) => <Stack {...props} direction={'row'} />)(() => ({
width: '100%',
flex: 1,
alignItems: 'flex-start',
gap: '0.5rem',
marginBottom: '0.5rem',
overflow: 'hidden',
'@media screen and (max-width: 320px)': {
alignSelf: 'flex-start',
},
}))
export const AppNameContainer = styled((props: BoxProps) => <Box {...props} />)(() => ({
display: 'flex',
flexDirection: 'column',
flex: 1,
overflow: 'auto',
})) }))

View File

@ -5,9 +5,7 @@ import { Box, Stack, Typography } from '@mui/material'
import { FC } from 'react' import { FC } from 'react'
import { StyledEmptyAppsBox } from '../styled' import { StyledEmptyAppsBox } from '../styled'
import { Button } from '@/shared/Button/Button' import { Button } from '@/shared/Button/Button'
import { call } from '@/utils/helpers/helpers'
import { swicCall } from '@/modules/swic'
import { useEnqueueSnackbar } from '@/hooks/useEnqueueSnackbar'
import { ItemApp } from './ItemApp' import { ItemApp } from './ItemApp'
type AppsProps = { type AppsProps = {
@ -16,22 +14,12 @@ type AppsProps = {
} }
export const Apps: FC<AppsProps> = ({ apps = [] }) => { export const Apps: FC<AppsProps> = ({ apps = [] }) => {
const notify = useEnqueueSnackbar()
// eslint-disable-next-line
async function deletePerm(id: string) {
call(async () => {
await swicCall('deletePerm', id)
notify('Perm deleted!', 'success')
})
}
const openAppStore = () => { const openAppStore = () => {
window.open('https://nostrapp.link', '_blank') window.open('https://nostrapp.link', '_blank')
} }
return ( return (
<Box flex={1} marginBottom={'1rem'} display={'flex'} flexDirection={'column'} overflow={'auto'}> <Box marginBottom={'1rem'} display={'flex'} flexDirection={'column'}>
<Stack direction={'row'} alignItems={'center'} justifyContent={'space-between'} marginBottom={'0.5rem'}> <Stack direction={'row'} alignItems={'center'} justifyContent={'space-between'} marginBottom={'0.5rem'}>
<SectionTitle>Connected apps</SectionTitle> <SectionTitle>Connected apps</SectionTitle>
<AppLink title="Discover Apps" onClick={openAppStore} /> <AppLink title="Discover Apps" onClick={openAppStore} />
@ -45,7 +33,7 @@ export const Apps: FC<AppsProps> = ({ apps = [] }) => {
</StyledEmptyAppsBox> </StyledEmptyAppsBox>
)} )}
<Stack gap={'0.5rem'} overflow={'auto'} flex={1}> <Stack gap={'0.5rem'} overflow={'auto'} flex={1} paddingBottom={'0.75rem'}>
{apps.map((a) => ( {apps.map((a) => (
<ItemApp {...a} key={a.appNpub} /> <ItemApp {...a} key={a.appNpub} />
))} ))}

View File

@ -1,6 +1,6 @@
import { FC } from 'react' import { FC } from 'react'
import { Warning } from '@/components/Warning/Warning' import { Warning } from '@/components/Warning/Warning'
import { CircularProgress, Stack, Typography } from '@mui/material' import { CircularProgress, Stack, Typography, TypographyProps, styled } from '@mui/material'
import AutoModeOutlinedIcon from '@mui/icons-material/AutoModeOutlined' import AutoModeOutlinedIcon from '@mui/icons-material/AutoModeOutlined'
type BackgroundSigningWarningProps = { type BackgroundSigningWarningProps = {
@ -12,13 +12,29 @@ export const BackgroundSigningWarning: FC<BackgroundSigningWarningProps> = ({ is
return ( return (
<Warning <Warning
message={ message={
<Stack direction={'row'} alignItems={'center'} gap={'1rem'}> <Stack gap={'0.25rem'} overflow={'auto'} width={'100%'}>
Enable background service {isEnabling ? <CircularProgress size={'1.5rem'} /> : null} <Typography variant="body1" noWrap fontWeight={'500'}>
Enable background service
</Typography>
<StyledHint>Please allow notifications for background operation.</StyledHint>
</Stack> </Stack>
} }
hint={<Typography variant="body2">Please allow notifications for background operation.</Typography>} icon={
icon={<AutoModeOutlinedIcon htmlColor="white" />} isEnabling ? (
<CircularProgress size={'1.5rem'} sx={{ color: '#fff' }} />
) : (
<AutoModeOutlinedIcon htmlColor="white" />
)
}
onClick={isEnabling ? undefined : onEnableBackSigning} onClick={isEnabling ? undefined : onEnableBackSigning}
/> />
) )
} }
const StyledHint = styled((props: TypographyProps) => <Typography variant="body2" {...props} />)(() => ({
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
textOverflow: 'ellipsis',
}))

View File

@ -1,4 +1,5 @@
import { Input, AppInputProps } from '@/shared/Input/Input' import { Input } from '@/shared/Input/Input'
import { AppInputProps } from '@/shared/Input/types'
import { Stack, StackProps, styled } from '@mui/material' import { Stack, StackProps, styled } from '@mui/material'
import { forwardRef } from 'react' import { forwardRef } from 'react'

View File

@ -1,7 +1,7 @@
import { useModalSearchParams } from '@/hooks/useModalSearchParams' import { useModalSearchParams } from '@/hooks/useModalSearchParams'
import { DbPending, DbPerm } from '@/modules/db' import { DbPending, DbPerm } from '@/modules/db'
import { MODAL_PARAMS_KEYS } from '@/types/modal' import { MODAL_PARAMS_KEYS } from '@/types/modal'
import { ACTION_TYPE } from '@/utils/consts' import { ACTION_TYPE, REQ_TTL } from '@/utils/consts'
import { useCallback, useEffect, useRef } from 'react' import { useCallback, useEffect, useRef } from 'react'
import { useSearchParams } from 'react-router-dom' import { useSearchParams } from 'react-router-dom'
@ -25,7 +25,7 @@ export const useTriggerConfirmModal = (npub: string, pending: DbPending[], perms
const isConfirmConnectModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_CONNECT) const isConfirmConnectModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_CONNECT)
const isConfirmEventModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_EVENT) const isConfirmEventModalOpened = getModalOpened(MODAL_PARAMS_KEYS.CONFIRM_EVENT)
const filteredPendingReqs = pending.filter((p) => p.npub === npub) const filteredPendingReqs = pending.filter((p) => p.npub === npub && p.timestamp > Date.now() - REQ_TTL)
const filteredPerms = perms.filter((p) => p.npub === npub) const filteredPerms = perms.filter((p) => p.npub === npub)
const npubConnectPerms = filteredPerms.filter((perm) => perm.perm === 'connect' || perm.perm === ACTION_TYPE.BASIC) const npubConnectPerms = filteredPerms.filter((perm) => perm.perm === 'connect' || perm.perm === ACTION_TYPE.BASIC)

View File

@ -1,4 +1,5 @@
import { Input, AppInputProps } from '@/shared/Input/Input' import { Input } from '@/shared/Input/Input'
import { AppInputProps } from '@/shared/Input/types'
import { Box, Button, ButtonProps, styled, Badge } from '@mui/material' import { Box, Button, ButtonProps, styled, Badge } from '@mui/material'
import { forwardRef } from 'react' import { forwardRef } from 'react'

View File

@ -1,13 +1,15 @@
import { Suspense, lazy } from 'react' import { Suspense, lazy } from 'react'
import { Route, Routes, Navigate } from 'react-router-dom' import { Route, Routes, Navigate } from 'react-router-dom'
import HomePage from '../pages/HomePage/Home.Page'
import { Layout } from '../layout/Layout' import { Layout } from '../layout/Layout'
import { CircularProgress, Stack } from '@mui/material' import { CircularProgress, Stack } from '@mui/material'
import CreatePage from '@/pages/CreatePage/Create.Page'
const KeyPage = lazy(() => import('../pages/KeyPage/Key.Page')) // Pages
const ConfirmPage = lazy(() => import('../pages/Confirm.Page')) import CreatePage from '@/pages/CreatePage/Create.Page'
const AppPage = lazy(() => import('../pages/AppPage/App.Page')) import HomePage from '../pages/HomePage/Home.Page'
import KeyPage from '../pages/KeyPage/Key.Page'
const ConfirmPage = lazy(() => import('@/pages/Confirm.Page'))
const AppPage = lazy(() => import('@/pages/AppPage/App.Page'))
const LoadingSpinner = () => ( const LoadingSpinner = () => (
<Stack height={'100%'} justifyContent={'center'} alignItems={'center'}> <Stack height={'100%'} justifyContent={'center'} alignItems={'center'}>

View File

@ -19,6 +19,9 @@ const StyledButton = styled(
const commonStyles = { const commonStyles = {
fontWeight: 500, fontWeight: 500,
borderRadius: '1rem', borderRadius: '1rem',
'@media screen and (max-width: 320px)': {
padding: '0.25rem 0.75rem',
},
} }
if (varianttype === 'secondary') { if (varianttype === 'secondary') {
return { return {

View File

@ -1,5 +1,6 @@
import { useRef } from 'react' import { useRef } from 'react'
import { Input, AppInputProps } from '../Input/Input' import { Input } from '../Input/Input'
import { AppInputProps } from '../Input/types'
export type DebounceProps = { export type DebounceProps = {
handleDebounce: (value: string) => void handleDebounce: (value: string) => void

View File

@ -9,7 +9,8 @@ export const StyledButton = styled((props: ButtonProps) => (
startIcon: 'icon', startIcon: 'icon',
}} }}
/> />
))(() => ({ ))(({ theme }) => ({
color: theme.palette.primary.main,
marginBottom: '0.5rem', marginBottom: '0.5rem',
borderRadius: '8px', borderRadius: '8px',
'&:is(:hover,:active)': { '&:is(:hover,:active)': {
@ -18,4 +19,7 @@ export const StyledButton = styled((props: ButtonProps) => (
'& .icon': { '& .icon': {
marginRight: '5px', marginRight: '5px',
}, },
'@media screen and (max-width: 320px)': {
marginBottom: '0.25rem',
},
})) }))

View File

@ -1,8 +1,11 @@
import React, { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import { StyledAppIcon, StyledAppImg } from './styled'
import ImageOutlinedIcon from '@mui/icons-material/ImageOutlined'
import { IIconApp } from './types'
const failedCache = new Map<string, boolean>() const failedCache = new Map<string, boolean>()
export const IconApp: FC<{ picture: string }> = ({ picture }) => { export const IconApp: FC<IIconApp> = ({ picture = '', alt, isRounded, isSmall, onClick, size, ...rest }) => {
const c = failedCache.get(picture) const c = failedCache.get(picture)
const [isFailed, setIsFailed] = useState(c !== undefined ? c : true) const [isFailed, setIsFailed] = useState(c !== undefined ? c : true)
@ -26,5 +29,21 @@ export const IconApp: FC<{ picture: string }> = ({ picture }) => {
} }
}, [picture]) }, [picture])
return <div>IconApp</div> return (
<StyledAppIcon isNotLoaded={isFailed} size={size} onClick={onClick} {...rest}>
{alt ? (
<StyledAppImg size={size} alt={alt} isSmall={isSmall} src={isFailed ? '' : picture}>
{isFailed && (
<div className="MuiAvatar-root MuiAvatar-square MuiAvatar-colorDefault">
{alt.substring(0, 1).toUpperCase()}
</div>
)}
</StyledAppImg>
) : (
<StyledAppImg size={size} alt={alt} isSmall={isSmall} src={isFailed ? '/' : picture}>
<ImageOutlinedIcon fontSize="inherit" />
</StyledAppImg>
)}
</StyledAppIcon>
)
} }

View File

@ -0,0 +1,53 @@
import { APP_NSEC_SIZE } from '@/utils/consts'
const SIZE_VALUE = {
[APP_NSEC_SIZE.BIG]: 70,
[APP_NSEC_SIZE.LARGE]: 56,
[APP_NSEC_SIZE.MEDIUM]: 40,
[APP_NSEC_SIZE.SMALL]: 36,
[APP_NSEC_SIZE.EXTRA_SMALL]: 30,
}
export const APP_SIZE_VALUE = {
[APP_NSEC_SIZE.BIG]: {
height: SIZE_VALUE[APP_NSEC_SIZE.BIG],
minWidth: SIZE_VALUE[APP_NSEC_SIZE.BIG],
maxWidth: SIZE_VALUE[APP_NSEC_SIZE.BIG],
},
[APP_NSEC_SIZE.LARGE]: {
height: SIZE_VALUE[APP_NSEC_SIZE.LARGE],
minWidth: SIZE_VALUE[APP_NSEC_SIZE.LARGE],
maxWidth: SIZE_VALUE[APP_NSEC_SIZE.LARGE],
},
[APP_NSEC_SIZE.MEDIUM]: {
height: SIZE_VALUE[APP_NSEC_SIZE.MEDIUM],
minWidth: SIZE_VALUE[APP_NSEC_SIZE.MEDIUM],
maxWidth: SIZE_VALUE[APP_NSEC_SIZE.MEDIUM],
},
[APP_NSEC_SIZE.SMALL]: {
height: SIZE_VALUE[APP_NSEC_SIZE.SMALL],
minWidth: SIZE_VALUE[APP_NSEC_SIZE.SMALL],
maxWidth: SIZE_VALUE[APP_NSEC_SIZE.SMALL],
},
[APP_NSEC_SIZE.EXTRA_SMALL]: {
height: SIZE_VALUE[APP_NSEC_SIZE.EXTRA_SMALL],
minWidth: SIZE_VALUE[APP_NSEC_SIZE.EXTRA_SMALL],
maxWidth: SIZE_VALUE[APP_NSEC_SIZE.EXTRA_SMALL],
},
}
const FONT_SIZE_VALUE = {
[APP_NSEC_SIZE.BIG]: 24,
[APP_NSEC_SIZE.LARGE]: 20,
[APP_NSEC_SIZE.MEDIUM]: 16,
[APP_NSEC_SIZE.SMALL]: 12,
[APP_NSEC_SIZE.EXTRA_SMALL]: 10,
}
export const APP_NAME_FONT_SIZE_VALUE = {
[APP_NSEC_SIZE.LARGE]: FONT_SIZE_VALUE[APP_NSEC_SIZE.LARGE],
[APP_NSEC_SIZE.BIG]: FONT_SIZE_VALUE[APP_NSEC_SIZE.BIG],
[APP_NSEC_SIZE.MEDIUM]: FONT_SIZE_VALUE[APP_NSEC_SIZE.MEDIUM],
[APP_NSEC_SIZE.SMALL]: FONT_SIZE_VALUE[APP_NSEC_SIZE.SMALL],
[APP_NSEC_SIZE.EXTRA_SMALL]: FONT_SIZE_VALUE[APP_NSEC_SIZE.EXTRA_SMALL],
}

View File

@ -0,0 +1,54 @@
import { Avatar, Box, styled } from '@mui/material'
import { forwardRef } from 'react'
import { IAvatarStyled, IBoxStyled } from './types'
import { grey } from '@mui/material/colors'
import { AppNostrSize } from '@/types/app-nsec'
import { APP_NAME_FONT_SIZE_VALUE, APP_SIZE_VALUE } from './const'
import { APP_NSEC_SIZE } from '@/utils/consts'
const color = grey[500]
const getVariantApp = (isRounded: boolean, size: AppNostrSize) => {
if (isRounded) {
return {
height: 34,
minWidth: 34,
maxWidth: 34,
borderRadius: '7px',
}
}
return APP_SIZE_VALUE[size]
}
export const StyledAppIcon = styled(
forwardRef<HTMLAnchorElement, IBoxStyled>(function BoxDisplayName(props, ref) {
return <Box ref={ref} {...props} />
})
)(({ theme, isNotLoaded, isRounded = false, size = APP_NSEC_SIZE.MEDIUM }) => ({
position: 'relative',
borderRadius: theme.shape.borderRadius,
overflow: 'hidden',
...getVariantApp(isRounded, size),
transition: theme.transitions.create(['border-color', 'transition']),
backgroundColor: isNotLoaded ? color : theme.palette.background.default,
boxSizing: 'border-box',
':active': {
borderColor: 'rgba(255, 255, 255, 0.3)',
},
}))
export const StyledAppImg = styled(function BoxDisplayName(props: IAvatarStyled) {
return <Avatar variant="square" {...props} />
})(({ isSmall = false, size = APP_NSEC_SIZE.MEDIUM }) => ({
position: 'absolute',
left: 0,
top: 0,
height: '100%',
width: '100%',
fontWeight: '500',
fontSize: APP_NAME_FONT_SIZE_VALUE[size],
'.MuiAvatar-img': {
objectFit: isSmall ? 'scale-down' : 'cover',
},
}))

View File

@ -0,0 +1,24 @@
import { AppNostrSize } from '@/types/app-nsec'
import { AvatarProps, BoxProps } from '@mui/material'
export type IconAppProps = {
size?: AppNostrSize
isRounded?: boolean
isSmall?: boolean
onClick?: () => void
isNotLoaded?: boolean
}
export type IconAppBase = {
picture: string
alt?: string
}
export type IIconApp = Omit<IconAppProps, 'isNotLoaded'> & IconAppBase
export type IBoxStyled = IconAppProps & BoxProps
export type IAvatarStyled = {
size?: AppNostrSize
isSmall?: boolean
} & AvatarProps

View File

@ -1,74 +1,34 @@
import { ReactNode, forwardRef } from 'react' import { ReactNode, forwardRef } from 'react'
import { import { FormHelperText, FormLabel, InputBase } from '@mui/material'
Box, import { StyledInputContainer } from './styled'
BoxProps, import { AppInputProps } from './types'
FormHelperText,
FormHelperTextProps,
FormLabel,
InputBase,
InputBaseProps,
styled,
} from '@mui/material'
export type AppInputProps = InputBaseProps & { const renderItem = <T,>(item: T, value: ReactNode) => (item ? value : null)
helperText?: string | ReactNode
helperTextProps?: FormHelperTextProps
containerProps?: BoxProps
label?: string
}
export const Input = forwardRef<HTMLInputElement, AppInputProps>( export const Input = forwardRef<HTMLInputElement, AppInputProps>(
({ helperText, containerProps, helperTextProps, label, ...props }, ref) => { ({ helperText, containerProps, helperTextProps, label, ...props }, ref) => {
return ( return (
<StyledInputContainer {...containerProps}> <StyledInputContainer {...containerProps}>
{label ? ( {renderItem(
label,
<FormLabel className="label" htmlFor={props.id}> <FormLabel className="label" htmlFor={props.id}>
{label} {label}
</FormLabel> </FormLabel>
) : null} )}
<InputBase <InputBase
autoComplete="off" autoComplete="off"
{...props} {...props}
classes={{ error: 'error', root: 'input_root', input: 'input', disabled: 'disabled' }} classes={{ error: 'error', root: 'input_root', input: 'input', disabled: 'disabled' }}
ref={ref} ref={ref}
/> />
{helperText ? ( {renderItem(
helperText,
<FormHelperText {...helperTextProps} className="helper_text"> <FormHelperText {...helperTextProps} className="helper_text">
{helperText} {helperText}
</FormHelperText> </FormHelperText>
) : null} )}
</StyledInputContainer> </StyledInputContainer>
) )
} }
) )
const StyledInputContainer = styled((props: BoxProps) => <Box {...props} />)(({ theme }) => {
const isDark = theme.palette.mode === 'dark'
return {
width: '100%',
'& > .input_root': {
background: isDark ? '#000000A8' : '#000',
color: theme.palette.common.white,
padding: '0.75rem 1rem',
borderRadius: '1rem',
border: '0.3px solid #FFFFFF54',
fontSize: '0.875rem',
'&.error': {
border: '0.3px solid ' + theme.palette.error.main,
},
},
'& .input:is(.disabled, &)': {
WebkitTextFillColor: '#ffffff80',
},
'& > .helper_text': {
margin: '0.5rem 0.5rem 0',
color: theme.palette.text.primary,
},
'& > .label': {
margin: '0 1rem 0.5rem',
display: 'block',
color: theme.palette.primary.main,
fontSize: '0.875rem',
},
}
})

View File

@ -0,0 +1,45 @@
import { Box, BoxProps, styled } from '@mui/material'
export const StyledInputContainer = styled((props: BoxProps) => <Box {...props} />)(({ theme }) => {
const isDark = theme.palette.mode === 'dark'
return {
width: '100%',
'& > .input_root': {
background: isDark ? '#000000A8' : '#000',
color: theme.palette.common.white,
padding: '0.75rem 1rem',
borderRadius: '1rem',
border: '0.3px solid #FFFFFF54',
fontSize: '0.875rem',
'&.error': {
border: '0.3px solid ' + theme.palette.error.main,
},
},
'& .input:is(.disabled, &)': {
WebkitTextFillColor: '#ffffff80',
},
'& > .helper_text': {
margin: '0.5rem 0.5rem 0',
color: theme.palette.text.primary,
},
'& > .label': {
margin: '0 1rem 0.5rem',
display: 'block',
color: theme.palette.primary.main,
fontSize: '0.875rem',
},
'@media screen and (max-width: 320px)': {
'& > .input_root': {
padding: '0.5rem 0.75rem',
borderRadius: '0.75rem',
},
'& > .label': {
margin: '0 0.25rem 0.25rem',
fontSize: '0.75rem',
},
'& > .helper_text': {
fontSize: '0.75rem',
},
},
}
})

View File

@ -0,0 +1,9 @@
import { BoxProps, FormHelperTextProps, InputBaseProps } from '@mui/material'
import { ReactNode } from 'react'
export type AppInputProps = InputBaseProps & {
helperText?: string | ReactNode
helperTextProps?: FormHelperTextProps
containerProps?: BoxProps
label?: string
}

View File

@ -1,8 +1,7 @@
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
import { Fade, IconButton, Typography } from '@mui/material' import { Fade, Typography } from '@mui/material'
import CopyToClipboard from 'react-copy-to-clipboard' import CopyToClipboard from 'react-copy-to-clipboard'
import { CopyIcon } from '@/assets' import { StyledContainer, StyledCopyButton } from './styled'
import { StyledContainer } from './styled'
type InputCopyButtonProps = { type InputCopyButtonProps = {
value: string value: string
@ -40,9 +39,7 @@ export const InputCopyButton: FC<InputCopyButtonProps> = ({ value, onCopy = () =
</Fade> </Fade>
)} )}
<CopyToClipboard text={value} onCopy={handleCopy}> <CopyToClipboard text={value} onCopy={handleCopy}>
<IconButton color="inherit"> <StyledCopyButton />
<CopyIcon />
</IconButton>
</CopyToClipboard> </CopyToClipboard>
</StyledContainer> </StyledContainer>
) )

View File

@ -1,7 +1,17 @@
import { Stack, StackProps, styled } from '@mui/material' import { IconButton, Stack, StackProps, styled } from '@mui/material'
import { CopyIcon } from '@/assets'
export const StyledContainer = styled((props: StackProps & { copied: number }) => ( export const StyledContainer = styled((props: StackProps & { copied: number }) => (
<Stack {...props} direction={'row'} alignItems={'center'} /> <Stack {...props} direction={'row'} alignItems={'center'} />
))(({ theme, copied }) => ({ ))(({ theme, copied }) => ({
color: copied ? theme.palette.success.main : theme.palette.textSecondaryDecorate.main, color: copied ? theme.palette.success.main : theme.palette.textSecondaryDecorate.main,
})) }))
export const StyledCopyButton = styled((props) => (
<IconButton color="inherit" {...props}>
<CopyIcon />
</IconButton>
))(() => ({
width: 40,
height: 40,
}))

6
src/types/app-nsec.ts Normal file
View File

@ -0,0 +1,6 @@
import { APP_NSEC_SIZE } from '@/utils/consts'
import { OverridableStringUnion } from './utils'
export type AppNostrSizeUnion = (typeof APP_NSEC_SIZE)[keyof typeof APP_NSEC_SIZE]
export type AppNostrSize = OverridableStringUnion<AppNostrSizeUnion>

10
src/types/utils.ts Normal file
View File

@ -0,0 +1,10 @@
export type OverridableStringUnion<T extends string> = GenerateStringUnion<DistributiveOmit<Record<T, true>>>
type GenerateStringUnion<T> = Extract<
{
[Key in keyof T]: true extends T[Key] ? Key : never
}[keyof T],
string
>
type DistributiveOmit<T> = T extends T ? T : never

View File

@ -11,6 +11,8 @@ export const KIND_RPC = 24133
export const RELOAD_STORAGE_KEY = 'reload' export const RELOAD_STORAGE_KEY = 'reload'
export const REQ_TTL = 60000 // 1 min
export enum ACTION_TYPE { export enum ACTION_TYPE {
BASIC = 'basic', BASIC = 'basic',
ADVANCED = 'advanced', ADVANCED = 'advanced',
@ -25,3 +27,11 @@ export const ACTIONS: { [type: string]: string } = {
nip04_encrypt: 'Encrypt message', nip04_encrypt: 'Encrypt message',
nip04_decrypt: 'Decrypt message', nip04_decrypt: 'Decrypt message',
} }
export const APP_NSEC_SIZE = {
BIG: 'big',
LARGE: 'large',
MEDIUM: 'medium',
SMALL: 'small',
EXTRA_SMALL: 'extra-small',
} as const

View File

@ -16,7 +16,7 @@ export const getShortenNpub = (npub = '') => {
} }
export const getProfileUsername = (profile: MetaEvent | null) => { export const getProfileUsername = (profile: MetaEvent | null) => {
if (!profile) return null if (!profile) return undefined
return profile?.info?.name || profile?.info?.display_name return profile?.info?.name || profile?.info?.display_name
} }
@ -72,6 +72,7 @@ export function isPackagePerm(perm: string, reqPerm: string) {
case 'sign_event:10002': case 'sign_event:10002':
case 'sign_event:30023': case 'sign_event:30023':
case 'sign_event:10000': case 'sign_event:10000':
case 'sign_event:27235':
return true return true
} }
} }