Skip to content

Commit 2c33b94

Browse files
committed
chore: implement passkey update functionality, add new help articles
1 parent 0e818d9 commit 2c33b94

File tree

8 files changed

+145
-39
lines changed

8 files changed

+145
-39
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
"@monaco-editor/react": "^4.7.0",
6060
"@noble/post-quantum": "^0.5.2",
6161
"@paralleldrive/cuid2": "2.2.2",
62-
"@remnawave/backend-contract": "2.2.35",
62+
"@remnawave/backend-contract": "2.2.37",
6363
"@simplewebauthn/browser": "^13.2.2",
6464
"@stablelib/base64": "^2.0.1",
6565
"@stablelib/x25519": "^2.0.1",

src/shared/api/hooks/passkeys/passkeys.hooks.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { DeletePasskeyCommand, VerifyPasskeyRegistrationCommand } from '@remnawave/backend-contract'
1+
import {
2+
DeletePasskeyCommand,
3+
UpdatePasskeyCommand,
4+
VerifyPasskeyRegistrationCommand
5+
} from '@remnawave/backend-contract'
26
import { notifications } from '@mantine/notifications'
37

48
import { createMutationHook } from '../../tsq-helpers'
@@ -34,3 +38,19 @@ export const useDeletePasskey = createMutationHook({
3438
}
3539
}
3640
})
41+
42+
export const useUpdatePasskey = createMutationHook({
43+
endpoint: UpdatePasskeyCommand.TSQ_url,
44+
bodySchema: UpdatePasskeyCommand.RequestSchema,
45+
responseSchema: UpdatePasskeyCommand.ResponseSchema,
46+
requestMethod: UpdatePasskeyCommand.endpointDetails.REQUEST_METHOD,
47+
rMutationParams: {
48+
onSuccess: () => {
49+
notifications.show({
50+
title: 'Passkey Updated',
51+
message: 'Passkey updated successfully',
52+
color: 'teal'
53+
})
54+
}
55+
}
56+
})

src/shared/ui/help-drawer/help-action-icon.shared.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { ActionIcon, Tooltip } from '@mantine/core'
1+
import { ActionIcon, ActionIconProps, Tooltip } from '@mantine/core'
22
import { TbQuestionMark } from 'react-icons/tb'
3+
import { IconBaseProps } from 'react-icons/lib'
34
import { useTranslation } from 'react-i18next'
45
import { memo } from 'react'
56

@@ -8,12 +9,14 @@ import { MODALS, useModalsStoreOpenWithData } from '@entities/dashboard/modal-st
89
import { THelpDrawerAvailableScreen } from './help-drawer.types'
910

1011
interface IProps {
12+
actionIconProps?: Omit<ActionIconProps, 'onClick'>
1113
hidden?: boolean
14+
iconProps?: IconBaseProps
1215
screen: THelpDrawerAvailableScreen
1316
}
1417

1518
export const HelpActionIconShared = memo((props: IProps) => {
16-
const { hidden, screen } = props
19+
const { actionIconProps, hidden, iconProps, screen } = props
1720

1821
const { t } = useTranslation()
1922

@@ -37,8 +40,9 @@ export const HelpActionIconShared = memo((props: IProps) => {
3740
onClick={handleOpenHelpDrawer}
3841
size="input-md"
3942
variant="light"
43+
{...actionIconProps}
4044
>
41-
<TbQuestionMark size={24} />
45+
<TbQuestionMark size={24} {...iconProps} />
4246
</ActionIcon>
4347
</Tooltip>
4448
)

src/shared/ui/help-drawer/help-drawer.types.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ export const HELP_DRAWER_AVAILABLE_SCREENS = {
33
PAGE_INTERNAL_SQUADS: 'PAGE_INTERNAL_SQUADS',
44
PAGE_EXTERNAL_SQUADS: 'PAGE_EXTERNAL_SQUADS',
55
PAGE_HOSTS: 'PAGE_HOSTS',
6-
PAGE_CONFIG_PROFILES: 'PAGE_CONFIG_PROFILES'
6+
PAGE_CONFIG_PROFILES: 'PAGE_CONFIG_PROFILES',
7+
AUTH_METHODS_TELEGRAM: 'AUTH_METHODS_TELEGRAM',
8+
AUTH_METHODS_YANDEX: 'AUTH_METHODS_YANDEX',
9+
AUTH_METHODS_GITHUB: 'AUTH_METHODS_GITHUB',
10+
AUTH_METHODS_POCKETID: 'AUTH_METHODS_POCKETID',
11+
AUTH_METHODS_PASSWORD: 'AUTH_METHODS_PASSWORD'
712
} as const
813

914
export type THelpDrawerAvailableScreen =

src/shared/ui/modals/rename-modal.shared.tsx

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
UpdateConfigProfileCommand,
33
UpdateExternalSquadCommand,
44
UpdateInternalSquadCommand,
5+
UpdatePasskeyCommand,
56
UpdateSubscriptionTemplateCommand
67
} from '@remnawave/backend-contract'
78
import { Button, Group, Modal, Stack, TextInput } from '@mantine/core'
@@ -14,12 +15,13 @@ import {
1415
useUpdateConfigProfile,
1516
useUpdateExternalSquad,
1617
useUpdateInternalSquad,
18+
useUpdatePasskey,
1719
useUpdateSubscriptionTemplate
1820
} from '@shared/api/hooks'
1921
import { MODALS, useModalClose, useModalState } from '@entities/dashboard/modal-store'
2022
import { queryClient } from '@shared/api/query-client'
2123

22-
type RenameType = 'configProfile' | 'externalSquad' | 'internalSquad' | 'template'
24+
type RenameType = 'configProfile' | 'externalSquad' | 'internalSquad' | 'passkey' | 'template'
2325

2426
interface IProps {
2527
renameFrom: RenameType
@@ -42,6 +44,12 @@ export function RenameModalShared({ renameFrom }: IProps) {
4244
})
4345
}
4446

47+
if (renameFrom === 'passkey') {
48+
return UpdatePasskeyCommand.RequestSchema.omit({ id: true }).safeParse({
49+
name: value
50+
})
51+
}
52+
4553
if (renameFrom === 'internalSquad') {
4654
return UpdateInternalSquadCommand.RequestSchema.omit({ uuid: true }).safeParse({
4755
name: value
@@ -118,6 +126,17 @@ export function RenameModalShared({ renameFrom }: IProps) {
118126
}
119127
)
120128

129+
const { mutate: updatePasskey, isPending: isUpdatingPasskey } = useUpdatePasskey({
130+
mutationFns: {
131+
onSuccess: () => {
132+
queryClient.refetchQueries({
133+
queryKey: QueryKeys.passkeys.getAllPasskeys.queryKey
134+
})
135+
handleModalClose()
136+
}
137+
}
138+
})
139+
121140
const handleSave = async () => {
122141
if (await nameField.validate()) return
123142

@@ -157,14 +176,24 @@ export function RenameModalShared({ renameFrom }: IProps) {
157176
name: nameField.getValue()
158177
}
159178
})
179+
} else if (renameFrom === 'passkey') {
180+
if (!internalState) return
181+
182+
updatePasskey({
183+
variables: {
184+
id: internalState.uuid,
185+
name: nameField.getValue()
186+
}
187+
})
160188
}
161189
}
162190

163191
const isLoading =
164192
isUpdatingInternalSquad ||
165193
isUpdatingConfigProfile ||
166194
isUpdatingTemplate ||
167-
isUpdatingExternalSquad
195+
isUpdatingExternalSquad ||
196+
isUpdatingPasskey
168197

169198
return (
170199
<Modal

src/widgets/remnawave-settings/authentification-settings-card/authentification-settings-card.widget.tsx

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
Accordion,
3-
Box,
43
Button,
54
Center,
65
Code,
@@ -28,6 +27,7 @@ import { TFunction } from 'i18next'
2827

2928
import { PasskeysDrawerComponent } from '@widgets/remnawave-settings/passkeys-settings-drawer/passkeys-drawer.component'
3029
import { useUpdateRemnawaveSettings } from '@shared/api/hooks/remnawave-settings/remnawave-settings.mutation.hooks'
30+
import { HelpActionIconShared, THelpDrawerAvailableScreen } from '@shared/ui/help-drawer'
3131
import { SettingsCardShared } from '@shared/ui/settings-card'
3232
import { QueryKeys } from '@shared/api/hooks/keys-factory'
3333
import { YandexLogo } from '@shared/ui/logos/yandex-logo'
@@ -225,7 +225,18 @@ export const AuthentificationSettingsCardWidget = (props: IProps) => {
225225
<Text fw={500}>{config.title}</Text>
226226
</Group>
227227
</Accordion.Control>
228-
<Box pr="xs">
228+
<Group gap="xs" justify="flex-end" pr="xs" wrap="nowrap">
229+
<HelpActionIconShared
230+
actionIconProps={{
231+
size: 'input-xs'
232+
}}
233+
iconProps={{
234+
size: 20
235+
}}
236+
screen={
237+
`AUTH_METHODS_${config.key.toUpperCase()}` as THelpDrawerAvailableScreen
238+
}
239+
/>
229240
<Switch
230241
color="teal.8"
231242
key={form.key(`oauth2Settings.${config.key}.enabled`)}
@@ -235,7 +246,7 @@ export const AuthentificationSettingsCardWidget = (props: IProps) => {
235246
type: 'checkbox'
236247
})}
237248
/>
238-
</Box>
249+
</Group>
239250
</Center>
240251

241252
<Accordion.Panel>
@@ -309,7 +320,16 @@ export const AuthentificationSettingsCardWidget = (props: IProps) => {
309320
</Text>
310321
</Group>
311322
</Accordion.Control>
312-
<Group justify="flex-end" pr="xs" wrap="nowrap">
323+
<Group gap="xs" justify="flex-end" pr="xs" wrap="nowrap">
324+
<HelpActionIconShared
325+
actionIconProps={{
326+
size: 'input-xs'
327+
}}
328+
iconProps={{
329+
size: 20
330+
}}
331+
screen="AUTH_METHODS_PASSWORD"
332+
/>
313333
<Switch
314334
color="teal.8"
315335
key={form.key('passwordSettings.enabled')}
@@ -407,7 +427,16 @@ export const AuthentificationSettingsCardWidget = (props: IProps) => {
407427
<Text fw={500}>Telegram</Text>
408428
</Group>
409429
</Accordion.Control>
410-
<Group justify="flex-end" pr="xs" wrap="nowrap">
430+
<Group gap="xs" justify="flex-end" pr="xs" wrap="nowrap">
431+
<HelpActionIconShared
432+
actionIconProps={{
433+
size: 'input-xs'
434+
}}
435+
iconProps={{
436+
size: 20
437+
}}
438+
screen="AUTH_METHODS_TELEGRAM"
439+
/>
411440
<Switch
412441
color="teal.8"
413442
key={form.key('tgAuthSettings.enabled')}
@@ -423,9 +452,6 @@ export const AuthentificationSettingsCardWidget = (props: IProps) => {
423452
<Accordion.Panel>
424453
<Stack gap="md">
425454
<TextInput
426-
description={t(
427-
'auth-settings.telegram.botToken.description'
428-
)}
429455
key={form.key('tgAuthSettings.botToken')}
430456
label={t('auth-settings.telegram.botToken.label')}
431457
placeholder={t(
@@ -436,9 +462,6 @@ export const AuthentificationSettingsCardWidget = (props: IProps) => {
436462

437463
<TagsInput
438464
clearable
439-
description={t(
440-
'auth-settings.telegram.adminIds.description'
441-
)}
442465
key={form.key('tgAuthSettings.adminIds')}
443466
label={t('auth-settings.telegram.adminIds.label')}
444467
placeholder={t(

src/widgets/remnawave-settings/passkeys-settings-drawer/passkeys-drawer.component.tsx

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
type PublicKeyCredentialCreationOptionsJSON,
2020
startRegistration
2121
} from '@simplewebauthn/browser'
22-
import { PiClockDuotone, PiKey, PiTrash } from 'react-icons/pi'
22+
import { PiClockDuotone, PiKey, PiPencil, PiTrash } from 'react-icons/pi'
2323
import { notifications } from '@mantine/notifications'
2424
import { TbFingerprint } from 'react-icons/tb'
2525
import { useTranslation } from 'react-i18next'
@@ -34,7 +34,9 @@ import {
3434
usePasskeyRegistrationOptions,
3535
usePasskeyRegistrationVerify
3636
} from '@shared/api/hooks'
37+
import { MODALS, useModalsStoreOpenWithData } from '@entities/dashboard/modal-store'
3738
import { CopyableFieldShared } from '@shared/ui/copyable-field/copyable-field'
39+
import { RenameModalShared } from '@shared/ui/modals/rename-modal.shared'
3840
import { formatDate } from '@shared/utils/misc/date'
3941
import { LoadingScreen } from '@shared/ui'
4042
import { queryClient } from '@shared/api'
@@ -48,6 +50,8 @@ export const PasskeysDrawerComponent = ({ onClose, opened }: IProps) => {
4850
const { t } = useTranslation()
4951

5052
const { data: passkeysData, isLoading } = useGetAllPasskeys()
53+
const openModalWithData = useModalsStoreOpenWithData()
54+
5155
const [isPasskeyRegistering, setIsPasskeyRegistering] = useState(false)
5256
const { mutate: deletePasskey, isPending: isDeleting } = useDeletePasskey({
5357
mutationFns: {
@@ -274,22 +278,42 @@ export const PasskeysDrawerComponent = ({ onClose, opened }: IProps) => {
274278
<Title order={5}>{passkey.name}</Title>
275279
</Group>
276280

277-
<Tooltip
278-
label={t(
279-
'passkeys-drawer.component.delete-passkey'
280-
)}
281-
>
282-
<ActionIcon
283-
color="red"
284-
disabled={isDeleting}
285-
loading={isDeleting}
286-
onClick={() => handleDelete(passkey.id)}
287-
size="lg"
288-
variant="light"
281+
<Group gap="xs">
282+
<Tooltip label={t('common.rename')}>
283+
<ActionIcon
284+
onClick={() =>
285+
openModalWithData(
286+
MODALS.RENAME_SQUAD_OR_CONFIG_PROFILE_MODAL,
287+
{
288+
name: passkey.name,
289+
uuid: passkey.id
290+
}
291+
)
292+
}
293+
size="lg"
294+
variant="light"
295+
>
296+
<PiPencil size={18} />
297+
</ActionIcon>
298+
</Tooltip>
299+
300+
<Tooltip
301+
label={t(
302+
'passkeys-drawer.component.delete-passkey'
303+
)}
289304
>
290-
<PiTrash size={18} />
291-
</ActionIcon>
292-
</Tooltip>
305+
<ActionIcon
306+
color="red"
307+
disabled={isDeleting}
308+
loading={isDeleting}
309+
onClick={() => handleDelete(passkey.id)}
310+
size="lg"
311+
variant="light"
312+
>
313+
<PiTrash size={18} />
314+
</ActionIcon>
315+
</Tooltip>
316+
</Group>
293317
</Group>
294318

295319
<CopyableFieldShared
@@ -317,6 +341,7 @@ export const PasskeysDrawerComponent = ({ onClose, opened }: IProps) => {
317341
</ScrollArea>
318342
)}
319343
</Flex>
344+
<RenameModalShared key="rename-passkey-modal" renameFrom="passkey" />
320345
</Drawer>
321346
)
322347
}

0 commit comments

Comments
 (0)