Skip to content
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

BA-2364: change password #239

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/authentication/modules/access/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ export type * from './useResetPassword/types'
export { default as useSignUp } from './useSignUp'
export type * from './useSignUp/types'

export { default as useChangeExpiredPassword } from './useChangeExpiredPassword'
export type * from './useChangeExpiredPassword/types'
export { default as useChangePassword } from './useChangePassword'
export type * from './useChangePassword/types'
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@ import {

import { z } from 'zod'

import useChangeExpiredPassword from '../index'
import useChangePassword from '../index'

describe('useChangeExpiredPassword', () => {
describe('useChangePassword', () => {
const currentPassword = '1234'
const password = '12345#Abcde'
const token = 'fake-token'
const changePasswordUrl = '/change-expired-password'
const changePasswordUrl = '/users/change-password'

afterEach(() => {
;(global.fetch as jest.Mock).mockClear() // Clear the mock between tests
Expand All @@ -27,16 +26,14 @@ describe('useChangeExpiredPassword', () => {
response: {
currentPassword,
newPassword: password,
token,
},
})

let hasOnSuccessRan = false

const { result } = renderHook(
() =>
useChangeExpiredPassword({
token,
useChangePassword({
defaultValues: {
currentPassword,
newPassword: password,
Expand Down Expand Up @@ -66,8 +63,7 @@ describe('useChangeExpiredPassword', () => {

const { result } = renderHook(
() =>
useChangeExpiredPassword({
token,
useChangePassword({
defaultValues: {
currentPassword,
newPassword: password,
Expand Down Expand Up @@ -101,8 +97,7 @@ describe('useChangeExpiredPassword', () => {

const { result } = renderHook(
() =>
useChangeExpiredPassword({
token,
useChangePassword({
defaultValues: {
currentPassword,
newPassword: password,
Expand Down Expand Up @@ -148,8 +143,7 @@ describe('useChangeExpiredPassword', () => {

const { result } = renderHook(
() =>
useChangeExpiredPassword({
token,
useChangePassword({
defaultValues: customDefaultValues,
validationSchema: customValidationSchema,
options: {
Expand All @@ -168,3 +162,54 @@ describe('useChangeExpiredPassword', () => {
expect(hasOnSuccessRan).toBe(true)
})
})

describe('useChangePassword with token for expired passwords', () => {
const currentPassword = '1234'
const password = 'abcABC@123456'
const token = 'fake-token'
const changePasswordUrl = '/change-expired-password'

afterEach(() => {
;(global.fetch as jest.Mock).mockClear() // Clear the mock between tests
})

// This is just to ensure that running with token has the same behavior as running without token
test('should run onSuccess', async () => {
// Mock the fetch call with a success response for POST method
mockFetch(changePasswordUrl, {
method: 'POST',
status: 200,
response: {
currentPassword,
newPassword: password,
token,
},
})

let hasOnSuccessRan = false

const { result } = renderHook(
() =>
useChangePassword({
token,
defaultValues: {
currentPassword,
newPassword: password,
confirmNewPassword: password,
},
options: {
onSuccess: () => {
hasOnSuccessRan = true
},
},
}),
{
wrapper: ComponentWithProviders,
},
)

await result.current.form.handleSubmit()

expect(hasOnSuccessRan).toBe(true)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PASSWORD_REGEX, ZOD_MESSAGE } from '@baseapp-frontend/utils'

import { z } from 'zod'

import type { ChangeExpiredPasswordForm } from './types'
import type { ChangePasswordForm } from './types'

export const DEFAULT_VALIDATION_SCHEMA = z
.object({
Expand All @@ -17,7 +17,7 @@ export const DEFAULT_VALIDATION_SCHEMA = z
path: ['confirmNewPassword'],
})

export const DEFAULT_INITIAL_VALUES: ChangeExpiredPasswordForm = {
export const DEFAULT_INITIAL_VALUES: ChangePasswordForm = {
currentPassword: '',
newPassword: '',
confirmNewPassword: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import { type SubmitHandler, useForm } from 'react-hook-form'

import AuthApi from '../../../services/auth'
import { DEFAULT_INITIAL_VALUES, DEFAULT_VALIDATION_SCHEMA } from './constants'
import type { ChangeExpiredPasswordForm, UseChangeExpiredPassword } from './types'
import type { ChangePasswordForm, UseChangePassword } from './types'

const useChangeExpiredPassword = ({
const useChangePassword = ({
token,
validationSchema = DEFAULT_VALIDATION_SCHEMA,
defaultValues = DEFAULT_INITIAL_VALUES,
ApiClass = AuthApi,
enableFormApiErrors = true,
options = {},
}: UseChangeExpiredPassword) => {
}: UseChangePassword) => {
const form = useForm({
defaultValues,
resolver: zodResolver(validationSchema),
Expand All @@ -24,7 +24,9 @@ const useChangeExpiredPassword = ({

const mutation = useMutation({
mutationFn: ({ currentPassword, newPassword }) =>
ApiClass.changeExpiredPassword({ currentPassword, newPassword, token }),
token
? ApiClass.changeExpiredPassword({ currentPassword, newPassword, token })
: ApiClass.changePassword({ currentPassword, newPassword }),
...options, // needs to be placed below all overridable options
onError: (err, variables, context) => {
options?.onError?.(err, variables, context)
Expand All @@ -37,7 +39,7 @@ const useChangeExpiredPassword = ({
},
})

const handleSubmit: SubmitHandler<ChangeExpiredPasswordForm> = async (values) => {
const handleSubmit: SubmitHandler<ChangePasswordForm> = async (values) => {
try {
await mutation.mutateAsync(values)
} catch (error) {
Expand All @@ -55,4 +57,4 @@ const useChangeExpiredPassword = ({
}
}

export default useChangeExpiredPassword
export default useChangePassword
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { z } from 'zod'

import AuthApi from '../../../services/auth'

type ApiClass = Pick<typeof AuthApi, 'changeExpiredPassword'>
type ApiClass = Pick<typeof AuthApi, 'changePassword' | 'changeExpiredPassword'>

export type ChangeExpiredPasswordForm = {
export type ChangePasswordForm = {
currentPassword: string
newPassword: string
confirmNewPassword: string
}

export interface UseChangeExpiredPassword {
token: string
export interface UseChangePassword {
token?: string
validationSchema?: z.ZodObject<z.ZodRawShape> | z.ZodEffects<z.ZodObject<z.ZodRawShape>>
defaultValues?: ChangeExpiredPasswordForm
options?: UseMutationOptions<void, unknown, ChangeExpiredPasswordForm, any>
defaultValues?: ChangePasswordForm
options?: UseMutationOptions<void, unknown, ChangePasswordForm, any>
ApiClass?: ApiClass
enableFormApiErrors?: boolean
}
8 changes: 8 additions & 0 deletions packages/authentication/services/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { baseAppFetch } from '@baseapp-frontend/utils'

import type {
ChangeExpiredPasswordRequest,
ChangePasswordRequest,
ForgotPasswordRequest,
LoginRequest,
LoginResponse,
Expand All @@ -26,6 +27,13 @@ export default class AuthApi {
return baseAppFetch(`/register`, { method: 'POST', body: request })
}

static changePassword({ currentPassword, newPassword }: ChangePasswordRequest) {
return baseAppFetch('/users/change-password', {
method: 'POST',
body: { currentPassword, newPassword },
})
}

static changeExpiredPassword({
currentPassword,
newPassword,
Expand Down
5 changes: 4 additions & 1 deletion packages/authentication/types/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,11 @@ export interface CustomJWTKeyNames {
refreshKeyName?: string
}

export interface ChangeExpiredPasswordRequest {
export interface ChangePasswordRequest {
currentPassword: string
newPassword: string
}

export interface ChangeExpiredPasswordRequest extends ChangePasswordRequest {
token: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { FC } from 'react'

import Svg, { Path } from 'react-native-svg'

import { useTheme } from '../../../../providers/native'
import { SvgIconProps } from '../types'

const AlertTriangleIcon: FC<SvgIconProps> = ({
isActive = false,
color,
width = '24',
height = '25',
...props
}) => {
const { colors } = useTheme()

const defaultColor = color ?? colors.object.high
const svgColor = isActive ? colors.primary.high : defaultColor

return (
<Svg width={width} height={height} viewBox="0 0 24 25" color={svgColor} fill="none" {...props}>
<Path
fillRule="evenodd"
clipRule="evenodd"
d="M22.5601 17.2714L14.8901 4.55137C14.2598 3.56537 13.1703 2.96875 12.0001 2.96875C10.8299 2.96875 9.74038 3.56537 9.1101 4.55137L1.4401 17.2714C0.888647 18.1906 0.869586 19.3343 1.3901 20.2714C1.99207 21.3265 3.11533 21.976 4.3301 21.9714H19.6701C20.8766 21.9842 21.9979 21.3511 22.6101 20.3114C23.1462 19.364 23.1271 18.2006 22.5601 17.2714ZM12.0001 17.9714C11.4478 17.9714 11.0001 17.5237 11.0001 16.9714C11.0001 16.4191 11.4478 15.9714 12.0001 15.9714C12.5524 15.9714 13.0001 16.4191 13.0001 16.9714C13.0001 17.5237 12.5524 17.9714 12.0001 17.9714ZM12.0001 14.9714C12.5524 14.9714 13.0001 14.5237 13.0001 13.9714V9.97137C13.0001 9.41909 12.5524 8.97137 12.0001 8.97137C11.4478 8.97137 11.0001 9.41909 11.0001 9.97137V13.9714C11.0001 14.5237 11.4478 14.9714 12.0001 14.9714Z"
fill="currentColor"
/>
</Svg>
)
}

export default AlertTriangleIcon
1 change: 1 addition & 0 deletions packages/design-system/components/native/icons/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as AlertTriangleIcon } from './AlertTriangleIcon'
export { default as BellIcon } from './BellIcon'
export { default as BiometricsIcon } from './BiometricsIcon'
export { default as BlockIcon } from './BlockIcon'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FC, useState } from 'react'

import { Ionicons } from '@expo/vector-icons'
import { LayoutChangeEvent } from 'react-native'
import { TextInput as PaperTextInput } from 'react-native-paper'

import { useTheme } from '../../../../providers/native'
Expand All @@ -14,16 +15,22 @@ const TextInput: FC<TextInputProps> = (props) => {
const { disabled, helperText } = props

const [isFocused, setIsFocused] = useState(false)
const [errorContainerWidth, setErrorContainerWidth] = useState(0)
const theme = useTheme()

const onLayout = (event: LayoutChangeEvent) => {
const { width } = event.nativeEvent.layout
setErrorContainerWidth(width)
}

const outlinedStyles = createOutlinedStyles(theme, {
isFocused,
isError: !!helperText,
isDisabled: disabled,
})

return (
<View style={styles.container}>
<View style={styles.container} onLayout={onLayout}>
<PaperTextInput
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
Expand All @@ -32,7 +39,9 @@ const TextInput: FC<TextInputProps> = (props) => {
{...props}
/>
{helperText && !disabled && (
<View style={styles.errorContainer}>
// Had to do this adjustment to the error container width because the error text was overflowing the container
// The 12px subtraction is to account for the padding of the error container
<View style={[styles.errorContainer, { width: errorContainerWidth - 12 }]}>
<Ionicons name="warning" size={15} color={theme.colors.error.main} />
<Text variant="caption" style={{ color: theme.colors.error.main }}>
{helperText}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const styles = StyleSheet.create({
gap: 8,
},
errorContainer: {
alignItems: 'center',
alignItems: 'flex-start',
flexDirection: 'row',
gap: 4,
paddingLeft: 12,
Expand Down
Loading