-
Notifications
You must be signed in to change notification settings - Fork 27
[PlatformAdmin] Add password setter component #882
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
idormenco
merged 66 commits into
commitglobal:main
from
imdeaconu:feature/add-password-setter-component
Mar 6, 2025
Merged
Changes from all commits
Commits
Show all changes
66 commits
Select commit
Hold shift + click to select a range
0fe3fbe
fix: make dropdown menus scrollable
imdeaconu 4ca1a44
fix: truncate overflowing table columns
imdeaconu 921aa13
Merge branch 'commitglobal:main' into main
imdeaconu e5a2869
Merge branch 'commitglobal:main' into main
imdeaconu 7866a67
Merge branch 'commitglobal:main' into main
imdeaconu 9ea6c42
Merge branch 'commitglobal:main' into main
imdeaconu 1bd449d
Merge branch 'commitglobal:main' into main
imdeaconu c9874d1
Squashed commit of the following:
imdeaconu b7715f3
Merge branch 'commitglobal:main' into main
imdeaconu 0facf65
Squashed commit of the following:
imdeaconu 67f681d
chore: remove unused import
imdeaconu 8d73252
chore: delete duplicated / unused classes
imdeaconu 63b21b9
Merge branch 'commitglobal:main' into main
imdeaconu 1892e6e
Merge branch 'commitglobal:main' into main
imdeaconu abb7c01
feature: add searching to MonitoringObserversTagFilter
imdeaconu 9d0b8ae
Merge branch 'commitglobal:main' into main
imdeaconu c9fcd3e
chore: update config files
imdeaconu 333ba49
Revert "[NGO Admin] Rewrite the tag selector component (#675)"
imdeaconu 580b68e
Merge branch 'main' of https://github.com/commitglobal/votemonitor
imdeaconu ba2dad9
Merge branch 'commitglobal:main' into main
imdeaconu eea4faa
Merge branch 'main' of https://github.com/commitglobal/votemonitor in…
imdeaconu 29b8163
Merge branch 'main' of https://github.com/commitglobal/votemonitor in…
imdeaconu 68a44ee
Merge branch 'commitglobal-main'
imdeaconu 7cf3244
Merge branch 'main' of https://github.com/commitglobal/votemonitor in…
imdeaconu b6abee7
Merge branch 'commitglobal-main-s1'
imdeaconu cc71856
Merge branch 'commitglobal:main' into main
imdeaconu e45ea22
Merge branch 'commitglobal:main' into main
imdeaconu 50d15b6
Merge branch 'commitglobal:main' into main
imdeaconu 1ed8e99
Merge branch 'commitglobal:main' into main
imdeaconu c2f1395
Merge branch 'commitglobal:main' into main
imdeaconu 2c6d5f0
Merge branch 'commitglobal:main' into main
imdeaconu 8c8e18f
Merge branch 'commitglobal:main' into main
imdeaconu db46a6d
Merge branch 'commitglobal:main' into main
imdeaconu d4b0263
Merge branch 'commitglobal:main' into main
imdeaconu 79864cd
Merge branch 'commitglobal:main' into main
imdeaconu 5ae7edf
Merge branch 'commitglobal:main' into main
imdeaconu c0fe98a
Merge branch 'commitglobal:main' into main
imdeaconu b7b3c5c
Merge branch 'commitglobal:main' into main
imdeaconu a892349
Merge branch 'commitglobal:main' into main
imdeaconu 2d98793
Merge branch 'commitglobal:main' into main
imdeaconu 66ba8d0
Merge branch 'commitglobal:main' into main
imdeaconu a86608d
Merge branch 'commitglobal:main' into main
imdeaconu aa1745c
Merge branch 'commitglobal:main' into main
imdeaconu 9942029
Merge branch 'commitglobal:main' into main
imdeaconu d855c24
Merge branch 'commitglobal:main' into main
imdeaconu 5a2b99d
Merge branch 'commitglobal:main' into main
imdeaconu e9ea9a3
Merge branch 'commitglobal:main' into main
imdeaconu fdaba4b
Merge branch 'commitglobal:main' into main
imdeaconu 777ab43
Merge branch 'commitglobal:main' into main
imdeaconu 15101d6
Merge branch 'commitglobal:main' into main
imdeaconu 9289b11
Merge branch 'commitglobal:main' into main
imdeaconu a82ea16
Merge branch 'commitglobal:main' into main
imdeaconu 914fc58
Merge branch 'commitglobal:main' into main
imdeaconu d9640c9
Merge branch 'commitglobal:main' into main
imdeaconu 3e82dc6
Merge branch 'commitglobal:main' into main
imdeaconu 315dfe9
Merge branch 'commitglobal:main' into main
imdeaconu 6e96bf5
Merge branch 'commitglobal:main' into main
imdeaconu 740f04b
WIP: rename prop for alternative filter key in Observer Tags and add …
imdeaconu fff3a05
WIP: fix push messages receipients query not invalidating after edits
imdeaconu a3771c5
invalidate targeted observers query after a morning observer is added
imdeaconu bd2e6bd
Merge remote-tracking branch 'upstream/main'
imdeaconu 3592375
Merge branch 'commitglobal:main' into main
imdeaconu c580139
Merge branch 'commitglobal:main' into main
imdeaconu bbb18d4
WIP: add password input
imdeaconu ea4602e
add PasswordSetterDialog
imdeaconu ba4bd40
fix types and extract logic for adding backend validation info to for…
imdeaconu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { AxiosError } from 'axios'; | ||
| import { FieldValues, Path, UseFormReturn } from 'react-hook-form'; | ||
| import { ProblemDetails } from './types'; | ||
|
|
||
| export const addFormValidationErrorsFromBackend = <T extends FieldValues>( | ||
| form: UseFormReturn<T>, | ||
| error: AxiosError<ProblemDetails> | ||
| ) => { | ||
| error?.response?.data.errors?.forEach((error) => { | ||
| form.setError(error.name as Path<T>, { type: 'custom', message: error.reason }); | ||
| }); | ||
| }; |
75 changes: 75 additions & 0 deletions
75
web/src/components/PasswordSetterDialog/PasswordSetterDialog.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| import { Button } from '@/components/ui/button'; | ||
| import { Dialog, DialogClose, DialogContent, DialogFooter, DialogTitle } from '@/components/ui/dialog'; | ||
| import { Form, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; | ||
| import { PasswordInput } from '@/components/ui/password-input'; | ||
| import { UseFormReturn } from 'react-hook-form'; | ||
| import { PasswordSetterFormData } from './usePasswordSetterDialog'; | ||
|
|
||
| export interface CreateNGODialogProps { | ||
| displayName: string; | ||
| open: boolean; | ||
| internalOnOpenChange: (open: any) => void; | ||
| form: UseFormReturn<PasswordSetterFormData>; | ||
| onSubmit: (values: PasswordSetterFormData) => void; | ||
| } | ||
|
|
||
| function PasswordSetterDialog({ displayName, open, form, internalOnOpenChange, onSubmit }: CreateNGODialogProps) { | ||
| return ( | ||
| <Dialog open={open} onOpenChange={internalOnOpenChange} modal={true}> | ||
| <DialogContent | ||
| className='min-w-[650px]' | ||
| onInteractOutside={(e) => { | ||
| e.preventDefault(); | ||
| }} | ||
| onEscapeKeyDown={(e) => { | ||
| e.preventDefault(); | ||
| }}> | ||
| <DialogTitle className='mb-3.5'>Set password for {displayName}</DialogTitle> | ||
| <div className='flex flex-col gap-3'> | ||
| <Form {...form}> | ||
| <form onSubmit={form.handleSubmit(onSubmit)} className='space-y-4'> | ||
| <FormField | ||
| control={form.control} | ||
| name='newPassword' | ||
| render={({ field, fieldState }) => ( | ||
| <FormItem> | ||
| <FormLabel>Password</FormLabel> | ||
| <PasswordInput placeholder='Password' {...field} {...fieldState} /> | ||
|
|
||
| <FormMessage /> | ||
| </FormItem> | ||
| )} | ||
| /> | ||
|
|
||
| <FormField | ||
| control={form.control} | ||
| name='confirmNewPassword' | ||
| render={({ field, fieldState }) => ( | ||
| <FormItem> | ||
| <FormLabel>Confirm password</FormLabel> | ||
| <PasswordInput placeholder='Confirm password' {...field} {...fieldState} /> | ||
|
|
||
| <FormMessage /> | ||
| </FormItem> | ||
| )} | ||
| /> | ||
|
|
||
| <DialogFooter> | ||
| <DialogClose asChild> | ||
| <Button className='text-purple-900 border border-purple-900 border-input bg-background hover:bg-purple-50 hover:text-purple-600'> | ||
| Cancel | ||
| </Button> | ||
| </DialogClose> | ||
| <Button title='Set password' type='submit' className='px-6'> | ||
| Set password | ||
| </Button> | ||
| </DialogFooter> | ||
| </form> | ||
| </Form> | ||
| </div> | ||
| </DialogContent> | ||
| </Dialog> | ||
| ); | ||
| } | ||
|
|
||
| export default PasswordSetterDialog; | ||
105 changes: 105 additions & 0 deletions
105
web/src/components/PasswordSetterDialog/usePasswordSetterDialog.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| import { authApi } from '@/common/auth-api'; | ||
| import { addFormValidationErrorsFromBackend } from '@/common/form-backend-validation'; | ||
| import { ProblemDetails } from '@/common/types'; | ||
| import { zodResolver } from '@hookform/resolvers/zod'; | ||
| import { useMutation } from '@tanstack/react-query'; | ||
| import { AxiosError } from 'axios'; | ||
| import { useCallback, useEffect, useState } from 'react'; | ||
| import { useForm } from 'react-hook-form'; | ||
| import { z } from 'zod'; | ||
| import { useDialog } from '../ui/use-dialog'; | ||
| import { toast } from '../ui/use-toast'; | ||
|
|
||
| const passwordSetterSchema = z | ||
| .object({ | ||
| newPassword: z.string().min(8, 'Password must be at least 8 characters long'), | ||
| confirmNewPassword: z.string(), | ||
| }) | ||
| .refine((data) => data.newPassword === data.confirmNewPassword, { | ||
| message: 'Passwords do not match', | ||
| path: ['confirmNewPassword'], | ||
| }); | ||
|
|
||
| type PasswordSetterUserInfoParams = { | ||
| userId: string; | ||
| displayName: string; | ||
| }; | ||
|
|
||
| export type PasswordSetterFormData = z.infer<typeof passwordSetterSchema>; | ||
|
|
||
| export const usePasswordSetterDialog = () => { | ||
| const [userId, setUserId] = useState<string>(''); | ||
| const [displayName, setDisplayName] = useState<string>(''); | ||
| const passwordSetterDialog = useDialog(); | ||
| const { open, onOpenChange } = passwordSetterDialog.dialogProps; | ||
|
|
||
| const form = useForm<PasswordSetterFormData>({ | ||
| resolver: zodResolver(passwordSetterSchema), | ||
| defaultValues: { | ||
| newPassword: '', | ||
| confirmNewPassword: '', | ||
| }, | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| if (form.formState.isSubmitSuccessful) { | ||
| form.reset(undefined, { keepValues: true }); | ||
| } | ||
| }, [form.formState.isSubmitSuccessful]); | ||
|
|
||
| const internalOnOpenChange = useCallback( | ||
| (open: boolean) => { | ||
| if (!open) { | ||
| form.reset({ | ||
| newPassword: '', | ||
| confirmNewPassword: '', | ||
| }); | ||
| } | ||
| onOpenChange(open); | ||
| }, | ||
| [onOpenChange] | ||
| ); | ||
|
|
||
| const setPasswordMutation = useMutation({ | ||
| mutationFn: (data: PasswordSetterFormData) => { | ||
| return authApi.post<PasswordSetterFormData>(`auth/set-password`, { | ||
| aspNetUserId: userId, | ||
| newPassword: data.newPassword, | ||
| }); | ||
| }, | ||
| onSuccess: () => { | ||
| form.reset({}); | ||
| internalOnOpenChange(false); | ||
| toast({ | ||
| title: 'Success', | ||
| description: 'Password set', | ||
| }); | ||
| }, | ||
|
|
||
| onError: (error: AxiosError<ProblemDetails>) => { | ||
| addFormValidationErrorsFromBackend<PasswordSetterFormData>(form, error); | ||
| toast({ | ||
| title: 'Error setting password', | ||
| description: 'Please contact Platform admins', | ||
| variant: 'destructive', | ||
| }); | ||
| }, | ||
| }); | ||
|
|
||
| const handlePasswordSet = (params: PasswordSetterUserInfoParams) => { | ||
| setUserId(params.userId); | ||
| setDisplayName(params.displayName); | ||
| passwordSetterDialog.trigger(); | ||
| }; | ||
|
|
||
| const onSubmit = (values: PasswordSetterFormData) => { | ||
| setPasswordMutation.mutate(values); | ||
| }; | ||
|
|
||
| const passwordSetterDialogProps = { open, form, userId, displayName, onOpenChange, internalOnOpenChange, onSubmit }; | ||
|
|
||
| return { | ||
| passwordSetterDialogProps, | ||
| handlePasswordSet, | ||
| }; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { Button } from '@/components/ui/button'; | ||
| import { Input, InputProps } from '@/components/ui/input'; | ||
| import { cn } from '@/lib/utils'; | ||
| import { EyeIcon, EyeOffIcon } from 'lucide-react'; | ||
| import { forwardRef, useState } from 'react'; | ||
|
|
||
| const PasswordInput = forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => { | ||
| const [showPassword, setShowPassword] = useState(false); | ||
| const disabled = props['value'] === '' || props['value'] === undefined || props['disabled']; | ||
|
|
||
| return ( | ||
| <div className='relative'> | ||
| <Input | ||
| type={showPassword ? 'text' : 'password'} | ||
| className={cn('hide-password-toggle pr-10', className)} | ||
| ref={ref} | ||
| {...props} | ||
| /> | ||
| <Button | ||
| type='button' | ||
| variant='ghost' | ||
| size='sm' | ||
| className='absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent' | ||
| onClick={() => setShowPassword((prev) => !prev)} | ||
| disabled={disabled}> | ||
| {showPassword && !disabled ? ( | ||
| <EyeIcon className='h-4 w-4' aria-hidden='true' /> | ||
| ) : ( | ||
| <EyeOffIcon className='h-4 w-4' aria-hidden='true' /> | ||
| )} | ||
| <span className='sr-only'>{showPassword ? 'Hide password' : 'Show password'}</span> | ||
| </Button> | ||
|
|
||
| {/* hides browsers password toggles */} | ||
| <style>{` | ||
| .hide-password-toggle::-ms-reveal, | ||
| .hide-password-toggle::-ms-clear { | ||
| visibility: hidden; | ||
| pointer-events: none; | ||
| display: none; | ||
| } | ||
| `}</style> | ||
| </div> | ||
| ); | ||
| }); | ||
| PasswordInput.displayName = 'PasswordInput'; | ||
|
|
||
| export { PasswordInput }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.