Skip to content

Commit

Permalink
refactor(fe): refactor settings page using context and hooks (#2274)
Browse files Browse the repository at this point in the history
* refactor(fe): refactor settings page using context and hooks

* refactor(fe): refactor settings page

* fix(fe): change type name to Profile

* fix(fe): no need to check response status

* fix(fe): add toast error when failed to check password

* fix(fe): remove unnecessary type use
  • Loading branch information
jihorobert authored Jan 10, 2025
1 parent 550601e commit a0b215b
Show file tree
Hide file tree
Showing 14 changed files with 299 additions and 265 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,30 @@ import { Input } from '@/components/shadcn/input'
import { cn } from '@/libs/utils'
import invisibleIcon from '@/public/icons/invisible.svg'
import visibleIcon from '@/public/icons/visible.svg'
import type { SettingsFormat } from '@/types/type'
import Image from 'next/image'
import React from 'react'
import type { FieldErrors, UseFormRegister } from 'react-hook-form'
import { FaCheck } from 'react-icons/fa6'
import { useSettingsContext } from './context'

interface CurrentPwSectionProps {
currentPassword: string
isCheckButtonClicked: boolean
isPasswordCorrect: boolean
setPasswordShow: React.Dispatch<React.SetStateAction<boolean>>
passwordShow: boolean
checkPassword: () => Promise<void>
register: UseFormRegister<SettingsFormat>
errors: FieldErrors<SettingsFormat>
updateNow: boolean
}

export default function CurrentPwSection({
currentPassword,
isCheckButtonClicked,
isPasswordCorrect,
setPasswordShow,
passwordShow,
checkPassword,
register,
errors,
updateNow
checkPassword
}: CurrentPwSectionProps) {
const {
passwordState: { passwordShow, setPasswordShow },
updateNow,
formState: { register, errors }
} = useSettingsContext()

return (
<>
<label className="-mb-4 mt-4 text-xs">Password</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { Input } from '@/components/shadcn/input'
import { useSettingsContext } from './context'

export default function IdSection() {
const { isLoading, defaultProfileValues } = useSettingsContext()

export default function IdSection({
isLoading,
defaultUsername
}: {
isLoading: boolean
defaultUsername: string
}) {
return (
<>
<label className="-mb-4 text-xs">ID</label>
<Input
placeholder={isLoading ? 'Loading...' : defaultUsername}
placeholder={isLoading ? 'Loading...' : defaultProfileValues.username}
disabled={true}
className="border-neutral-300 text-neutral-600 placeholder:text-neutral-400 disabled:bg-neutral-200"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,16 @@ import { majors } from '@/libs/constants'
import { cn } from '@/libs/utils'
import React from 'react'
import { FaChevronDown, FaCheck } from 'react-icons/fa6'
import { useSettingsContext } from './context'

interface MajorSectionProps {
majorOpen: boolean
setMajorOpen: React.Dispatch<React.SetStateAction<boolean>>
majorValue: string
setMajorValue: React.Dispatch<React.SetStateAction<string>>
updateNow: boolean
isLoading: boolean
defaultProfileValues: {
major?: string
}
}
export default function MajorSection() {
const {
isLoading,
updateNow,
majorState: { majorOpen, setMajorOpen, majorValue, setMajorValue },
defaultProfileValues
} = useSettingsContext()

export default function MajorSection({
majorOpen,
setMajorOpen,
majorValue,
setMajorValue,
updateNow,
isLoading,
defaultProfileValues
}: MajorSectionProps) {
return (
<>
<label className="-mb-4 mt-2 text-xs">First Major</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import { Input } from '@/components/shadcn/input'
import { cn } from '@/libs/utils'
import type { SettingsFormat } from '@/types/type'
import type { FieldErrors, UseFormRegister } from 'react-hook-form'
import { useSettingsContext } from './context'

interface NameSectionProps {
isLoading: boolean
updateNow: boolean
defaultProfileValues: { userProfile?: { realName?: string } }
register: UseFormRegister<SettingsFormat>
errors: FieldErrors<SettingsFormat>
realName: string
}

export default function NameSection({
isLoading,
updateNow,
defaultProfileValues,
register,
errors,
realName
}: NameSectionProps) {
export default function NameSection({ realName }: NameSectionProps) {
const {
isLoading,
updateNow,
defaultProfileValues,
formState: { register, errors }
} = useSettingsContext()

return (
<>
<label className="-mb-4 text-xs">Name</label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,29 @@ import { Input } from '@/components/shadcn/input'
import { cn } from '@/libs/utils'
import invisibleIcon from '@/public/icons/invisible.svg'
import visibleIcon from '@/public/icons/visible.svg'
import type { SettingsFormat } from '@/types/type'
import Image from 'next/image'
import React from 'react'
import type { FieldErrors, UseFormRegister } from 'react-hook-form'
import { useSettingsContext } from './context'

interface NewPwSectionProps {
newPasswordShow: boolean
setNewPasswordShow: React.Dispatch<React.SetStateAction<boolean>>
newPasswordAble: boolean
isPasswordsMatch: boolean
newPassword: string
confirmPassword: string
updateNow: boolean
register: UseFormRegister<SettingsFormat>
errors: FieldErrors<SettingsFormat>
}

export default function NewPwSection({
newPasswordShow,
setNewPasswordShow,
newPasswordAble,
isPasswordsMatch,
newPassword,
confirmPassword,
updateNow,
register,
errors
confirmPassword
}: NewPwSectionProps) {
const {
passwordState: { newPasswordShow, setNewPasswordShow },
updateNow,
formState: { register, errors }
} = useSettingsContext()

return (
<>
<div className="flex items-center gap-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,28 @@ import visibleIcon from '@/public/icons/visible.svg'
import type { SettingsFormat } from '@/types/type'
import Image from 'next/image'
import React from 'react'
import type { UseFormRegister, UseFormGetValues } from 'react-hook-form'
import type { UseFormGetValues } from 'react-hook-form'
import { useSettingsContext } from './context'

interface ReEnterNewPwSectionProps {
confirmPasswordShow: boolean
setConfirmPasswordShow: React.Dispatch<React.SetStateAction<boolean>>
newPasswordAble: boolean
updateNow: boolean
register: UseFormRegister<SettingsFormat>
getValues: UseFormGetValues<SettingsFormat>
confirmPassword: string
isPasswordsMatch: boolean
}

export default function ReEnterNewPwSection({
confirmPasswordShow,
setConfirmPasswordShow,
newPasswordAble,
updateNow,
register,
getValues,
confirmPassword,
isPasswordsMatch
}: ReEnterNewPwSectionProps) {
const {
updateNow,
passwordState: { confirmPasswordShow, setConfirmPasswordShow },
formState: { register }
} = useSettingsContext()

return (
<>
{/* Re-enter new password */}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { Button } from '@/components/shadcn/button'
import { useSettingsContext } from './context'

interface SaveButtonProps {
updateNow: boolean
saveAbleUpdateNow: boolean
saveAble: boolean
onSubmitClick: () => void
}

export default function SaveButton({
updateNow,
saveAbleUpdateNow,
saveAble,
onSubmitClick
}: SaveButtonProps) {
const { updateNow } = useSettingsContext()

return (
<div className="mt-2 text-end">
<Button
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
import { Input } from '@/components/shadcn/input'
import { cn } from '@/libs/utils'
import type { SettingsFormat } from '@/types/type'
import type { FieldErrors, UseFormRegister } from 'react-hook-form'
import { useSettingsContext } from './context'

interface StudentIdSectionProps {
studentId: string
updateNow: boolean
isLoading: boolean
errors: FieldErrors<SettingsFormat>
register: UseFormRegister<SettingsFormat>
defaultProfileValues: {
studentId?: string
}
}

export default function StudentIdSection({
studentId,
updateNow,
isLoading,
errors,
register,
defaultProfileValues
}: StudentIdSectionProps) {
export default function StudentIdSection({ studentId }: StudentIdSectionProps) {
const {
isLoading,
updateNow,
defaultProfileValues,
formState: { register, errors }
} = useSettingsContext()
return (
<>
<label className="-mb-4 mt-2 text-xs">Student ID</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
export default function TopicSection({ updateNow }: { updateNow: boolean }) {
import { useSettingsContext } from './context'

export default function TopicSection() {
const { updateNow } = useSettingsContext()

return (
<>
<h1 className="-mb-1 text-center text-2xl font-bold">Settings</h1>
Expand Down
56 changes: 56 additions & 0 deletions apps/frontend/app/(client)/(main)/settings/_components/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { SettingsFormat } from '@/types/type'
import { createContext, useContext } from 'react'
import type { FieldErrors, UseFormRegister } from 'react-hook-form'

export interface Profile {
username: string // ID
userProfile: {
realName: string
}
studentId: string
major: string
}

interface PasswordState {
passwordShow: boolean
setPasswordShow: React.Dispatch<React.SetStateAction<boolean>>
newPasswordShow: boolean
setNewPasswordShow: React.Dispatch<React.SetStateAction<boolean>>
confirmPasswordShow: boolean
setConfirmPasswordShow: React.Dispatch<React.SetStateAction<boolean>>
}

interface MajorState {
majorOpen: boolean
setMajorOpen: React.Dispatch<React.SetStateAction<boolean>>
majorValue: string
setMajorValue: React.Dispatch<React.SetStateAction<string>>
}

interface FormState {
register: UseFormRegister<SettingsFormat>
errors: FieldErrors<SettingsFormat>
}

export type SettingsContextType = {
defaultProfileValues: Profile
passwordState: PasswordState
majorState: MajorState
formState: FormState
updateNow: boolean
isLoading: boolean
}

const SettingsContext = createContext<SettingsContextType | undefined>(
undefined
)
export const SettingsProvider = SettingsContext.Provider

// useSettingsContext custom hook
export const useSettingsContext = () => {
const context = useContext(SettingsContext)
if (!context) {
throw new Error('useSettingsContext must be used within a SettingsProvider')
}
return context
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { safeFetcher } from '@/libs/utils'
import { useState } from 'react'
import { toast } from 'sonner'

interface UseCheckPasswordResult {
isPasswordCorrect: boolean
newPasswordAble: boolean
isCheckButtonClicked: boolean
checkPassword: () => Promise<void>
}

/**
* Hook that returns whether a new password can be set after verifying the current password
* @param defaultProfileValues Default profile values
* @param currentPassword The password currently entered by the user
*/
export const useCheckPassword = (
defaultProfileValues: { username: string },
currentPassword: string
): UseCheckPasswordResult => {
const [isPasswordCorrect, setIsPasswordCorrect] = useState(false)
const [newPasswordAble, setNewPasswordAble] = useState(false)
const [isCheckButtonClicked, setIsCheckButtonClicked] = useState(false)

const checkPassword = async () => {
setIsCheckButtonClicked(true)
try {
const response = await safeFetcher.post('auth/login', {
json: {
username: defaultProfileValues.username,
password: currentPassword
}
})

setIsPasswordCorrect(true)
setNewPasswordAble(true)
} catch {
toast.error('Failed to check password')
console.error('Failed to check password')
}
}

return {
isPasswordCorrect,
newPasswordAble,
isCheckButtonClicked,
checkPassword
}
}
Loading

0 comments on commit a0b215b

Please sign in to comment.