Skip to content

Commit

Permalink
Profil - Uprava udajov (#160)
Browse files Browse the repository at this point in the history
* Add edit profile buttons

* Move School subform into own component

* `yarn add usehooks-ts`

* Add ProfileForm and improve SchoolSubForm

* Fix typescript errror and cyclic imports

* remove async/await where wasn't neccesary
  • Loading branch information
Matushl authored Nov 11, 2023
1 parent 4ff0f21 commit d27e21d
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 107 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@
"remark-gfm": "^3.0.1",
"remark-math": "^5.1.1",
"typescript": "^5.0.4",
"unstated-next": "^1.1.0"
"unstated-next": "^1.1.0",
"usehooks-ts": "^2.9.1"
},
"devDependencies": {
"@svgr/webpack": "^8.0.1",
Expand Down
4 changes: 2 additions & 2 deletions src/components/PageLayout/LoginForm/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export const LoginForm: FC<ILoginForm> = ({closeOverlay}) => {
const {login} = AuthContainer.useContainer()
const {handleSubmit, control} = useForm<LoginFormValues>({defaultValues})

const onSubmit: SubmitHandler<LoginFormValues> = async (data) => {
await login({data, onSuccess: closeOverlay})
const onSubmit: SubmitHandler<LoginFormValues> = (data) => {
login({data, onSuccess: closeOverlay})
}

const requiredRule = {required: '* Toto pole nemôže byť prázdne.'}
Expand Down
29 changes: 22 additions & 7 deletions src/components/Profile/ProfileDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import {FC} from 'react'

import {Profile} from '@/types/api/personal'
import {AuthContainer} from '@/utils/AuthContainer'
import {useSeminarInfo} from '@/utils/useSeminarInfo'

import {Button, Link} from '../Clickable/Clickable'
import styles from './ProfileDetail.module.scss'

type ProfileLineInput = {
Expand All @@ -25,6 +27,7 @@ const ProfileLine: FC<ProfileLineInput> = ({label, value}) => {

export const ProfileDetail: FC = () => {
const {isAuthed} = AuthContainer.useContainer()
const {seminar} = useSeminarInfo()

const {data} = useQuery({
queryKey: ['personal', 'profiles', 'myprofile'],
Expand All @@ -34,13 +37,25 @@ export const ProfileDetail: FC = () => {
const profile = data?.data

return (
<Stack spacing={2}>
<ProfileLine label={'meno'} value={profile?.first_name + ' ' + profile?.last_name} />
<ProfileLine label={'e-mail'} value={profile?.email} />
<ProfileLine label={'škola'} value={profile?.school.verbose_name} />
<ProfileLine label={'ročník'} value={profile?.grade_name} />
<ProfileLine label={'tel. č.'} value={profile?.phone || '-'} />
<ProfileLine label={'tel. č. na rodiča'} value={profile?.parent_phone || '-'} />
<Stack>
<Stack spacing={2}>
<ProfileLine label={'meno'} value={profile?.first_name + ' ' + profile?.last_name} />
<ProfileLine label={'e-mail'} value={profile?.email} />
<ProfileLine label={'škola'} value={profile?.school.verbose_name} />
<ProfileLine label={'ročník'} value={profile?.grade_name} />
<ProfileLine label={'tel. č.'} value={profile?.phone || '-'} />
<ProfileLine label={'tel. č. na rodiča'} value={profile?.parent_phone || '-'} />
</Stack>
<Stack direction={'row'} mt={3} spacing={2}>
<Link href={`/${seminar}/profil/uprava`}>upraviť údaje</Link>
<Button
onClick={() => {
console.log('TODO: modal so zmenou hesla')

Check warning on line 53 in src/components/Profile/ProfileDetail.tsx

View workflow job for this annotation

GitHub Actions / branch-test

Unexpected console statement
}}
>
zmeniť heslo
</Button>
</Stack>
</Stack>
)
}
121 changes: 121 additions & 0 deletions src/components/Profile/ProfileForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {useMutation, useQuery} from '@tanstack/react-query'
import axios from 'axios'
import {useRouter} from 'next/router'
import {FC} from 'react'
import {SubmitHandler, useForm} from 'react-hook-form'

import styles from '@/components/FormItems/Form.module.scss'
import {FormInput} from '@/components/FormItems/FormInput/FormInput'
import {SelectOption} from '@/components/FormItems/FormSelect/FormSelect'
import {IGeneralPostResponse} from '@/types/api/general'
import {Profile} from '@/types/api/personal'
import {AuthContainer} from '@/utils/AuthContainer'
import {useSeminarInfo} from '@/utils/useSeminarInfo'

import {Button} from '../Clickable/Clickable'
import {SchoolSubForm, SchoolSubFormValues} from '../SchoolSubForm/SchoolSubForm'

interface ProfileFormValues extends SchoolSubFormValues {
first_name?: string
last_name?: string
phone?: string
parent_phone?: string
}

const defaultValues: ProfileFormValues = {
first_name: '',
last_name: '',
phone: '',
parent_phone: '',
new_school_description: '',
without_school: false,
school: null,
school_not_found: false,
grade: '',
}

export const ProfileForm: FC = () => {
const {isAuthed} = AuthContainer.useContainer()

const {data} = useQuery({
queryKey: ['personal', 'profiles', 'myprofile'],
queryFn: () => axios.get<Profile>(`/api/personal/profiles/myprofile`),
enabled: isAuthed,
})
const profile = data?.data
const profileValues: ProfileFormValues = {
first_name: profile?.first_name,
last_name: profile?.last_name,
phone: profile?.phone ?? '',
parent_phone: profile?.parent_phone ?? '',
new_school_description: '',
without_school: profile?.school_id === 1,
school: ({id: profile?.school.code, label: profile?.school.verbose_name} as SelectOption) ?? null,
school_not_found: profile?.school_id === 0,
grade: profile?.grade ?? '',
}

const {handleSubmit, control, watch, setValue} = useForm<ProfileFormValues>({
defaultValues,
values: profileValues,
})

watch(['first_name'])

const scrollToTop = () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
})
}

const router = useRouter()

const {seminar} = useSeminarInfo()

const transformFormData = (data: ProfileFormValues) => ({
profile: {
first_name: data.first_name,
last_name: data.last_name,
school: data.school?.id,
phone: data.phone,
parent_phone: data.parent_phone,
grade: data.grade,
},
new_school_description: data.new_school_description || '',
})

const {mutate: submitFormData} = useMutation({
mutationFn: (data: ProfileFormValues) => {
return axios.put<IGeneralPostResponse>(`/api/user/user`, transformFormData(data))
},
onSuccess: () => router.push(`/${seminar}/profil`),
})

const onSubmit: SubmitHandler<ProfileFormValues> = (data) => {
submitFormData(data)
}

const requiredRule = {required: '* Toto pole nemôže byť prázdne.'}
const phoneRule = {
validate: (val?: string) => {
if (val && !/^(\+\d{10,12})$/u.test(val.replaceAll(/\s+/gu, '')))
return '* Zadaj telefónne číslo vo formáte validnom formáte +421 123 456 789 alebo +421123456789.'
},
}
return (
<div>
<form className={styles.form} onSubmit={handleSubmit(onSubmit)}>
<FormInput control={control} name="first_name" label="krstné meno*" rules={requiredRule} />
<FormInput control={control} name="last_name" label="priezvisko*" rules={requiredRule} />
<SchoolSubForm control={control} watch={watch} setValue={setValue} />
<FormInput control={control} name="phone" label="telefónne číslo" rules={phoneRule} />
<FormInput control={control} name="parent_phone" label="telefónne číslo na rodiča" rules={phoneRule} />
<p style={{fontWeight: 'bold'}}>* takto označéné polia sú povinné</p>
<Button type="submit" onClick={scrollToTop}>
Uložiť údaje
</Button>
</form>
</div>
)
}
108 changes: 11 additions & 97 deletions src/components/RegisterForm/RegisterForm.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,27 @@
import {useMutation, useQuery} from '@tanstack/react-query'
import {useMutation} from '@tanstack/react-query'
import axios from 'axios'
import {useRouter} from 'next/router'
import {FC, useEffect, useRef} from 'react'
import {FC} from 'react'
import {SubmitHandler, useForm} from 'react-hook-form'

import styles from '@/components/FormItems/Form.module.scss'
import {FormAutocomplete} from '@/components/FormItems/FormAutocomplete/FormAutocomplete'
import {FormCheckbox} from '@/components/FormItems/FormCheckbox/FormCheckbox'
import {FormInput} from '@/components/FormItems/FormInput/FormInput'
import {FormSelect, SelectOption} from '@/components/FormItems/FormSelect/FormSelect'
import {IGrade} from '@/types/api/competition'
import {IGeneralPostResponse} from '@/types/api/general'
import {ISchool} from '@/types/api/personal'
import {useSeminarInfo} from '@/utils/useSeminarInfo'

import {Button} from '../Clickable/Clickable'
import {SchoolSubForm, SchoolSubFormValues} from '../SchoolSubForm/SchoolSubForm'

type RegisterFormValues = {
interface RegisterFormValues extends SchoolSubFormValues {
email?: string
password1?: string
password2?: string
first_name?: string
last_name?: string
phone?: string
parent_phone?: string
new_school_description?: string
without_school: boolean
school?: SelectOption | null
school_not_found: boolean
grade: number | ''
gdpr: boolean
gdpr?: boolean
}

const defaultValues: RegisterFormValues = {
Expand All @@ -49,8 +41,10 @@ const defaultValues: RegisterFormValues = {
}

export const RegisterForm: FC = () => {
const {handleSubmit, control, watch, setValue, getValues} = useForm<RegisterFormValues>({defaultValues})
const [school_not_found, without_school] = watch(['school_not_found', 'without_school'])
const {handleSubmit, control, watch, setValue, getValues} = useForm<RegisterFormValues>({
defaultValues,
values: defaultValues,
})

const scrollToTop = () => {
window.scrollTo({
Expand All @@ -59,58 +53,8 @@ export const RegisterForm: FC = () => {
})
}

const otherSchoolItem = useRef<SelectOption>()
const withoutSchoolItem = useRef<SelectOption>()

const router = useRouter()

// načítanie ročníkov z BE, ktorými vyplníme FormSelect s ročníkmi
const {data: gradesData} = useQuery({
queryKey: ['competition', 'grade'],
queryFn: () => axios.get<IGrade[]>(`/api/competition/grade`),
})
const grades = gradesData?.data ?? []
const gradeItems: SelectOption[] = grades.map(({id, name}) => ({id, label: name}))

// načítanie škôl z BE, ktorými vyplníme FormAutocomplete so školami
const {data: schoolsData} = useQuery({
queryKey: ['personal', 'schools'],
queryFn: () => axios.get<ISchool[]>(`/api/personal/schools`),
})
const schools = schoolsData?.data ?? []
const allSchoolItems: SelectOption[] = schools.map(({code, city, name, street}) => ({
id: code,
label: city ? `${name} ${street}, ${city}` : name,
}))
const emptySchoolItems = allSchoolItems.filter(({id}) => [0, 1].includes(id))
otherSchoolItem.current = emptySchoolItems.find(({id}) => id === 0)
withoutSchoolItem.current = emptySchoolItems.find(({id}) => id === 1)
const schoolItems = allSchoolItems.filter(({id}) => ![0, 1].includes(id))

// predvyplnenie/zmazania hodnôt pri zakliknutí checkboxu pre užívateľa po škole
useEffect(() => {
if (without_school) {
setValue('school', withoutSchoolItem.current)
setValue('grade', 13)
setValue('school_not_found', false)
} else {
setValue('school', null)
setValue('grade', '')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [without_school])

// predvyplnenie/zmazania hodnôt pri zakliknutí checkboxu pre neznámu školu
useEffect(() => {
if (school_not_found) {
setValue('school', otherSchoolItem.current)
} else if (!without_school) {
setValue('school', null)
setValue('grade', '')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [school_not_found])

const {seminar} = useSeminarInfo()

const transformFormData = (data: RegisterFormValues) => ({
Expand All @@ -136,7 +80,7 @@ export const RegisterForm: FC = () => {
onSuccess: () => router.push(`${router.asPath}/../verifikacia`),
})

const onSubmit: SubmitHandler<RegisterFormValues> = async (data) => {
const onSubmit: SubmitHandler<RegisterFormValues> = (data) => {
submitFormData(data)
}

Expand Down Expand Up @@ -189,37 +133,7 @@ export const RegisterForm: FC = () => {
/>
<FormInput control={control} name="first_name" label="krstné meno*" rules={requiredRule} />
<FormInput control={control} name="last_name" label="priezvisko*" rules={requiredRule} />
<FormCheckbox control={control} name="without_school" label="nie som študent základnej ani strednej školy." />
<FormAutocomplete
control={control}
name="school"
label="škola*"
options={school_not_found || without_school ? emptySchoolItems : schoolItems}
disabled={!schoolItems.length || school_not_found || without_school}
rules={requiredRule}
/>
<FormCheckbox
control={control}
name="school_not_found"
label="moja škola sa v zozname nenachádza."
disabled={without_school}
/>
{school_not_found && (
<FormInput
control={control}
name="new_school_description"
label="povedz nám, na akú školu chodíš, aby sme ti ju mohli pridať*"
rules={school_not_found ? requiredRule : {}}
/>
)}
<FormSelect
control={control}
name="grade"
label="ročník*"
options={gradeItems.filter(({id}) => id !== 13 || without_school)}
disabled={!gradeItems.length || without_school}
rules={requiredRule}
/>
<SchoolSubForm control={control} watch={watch} setValue={setValue} />
<FormInput control={control} name="phone" label="telefónne číslo" rules={phoneRule} />
<FormInput control={control} name="parent_phone" label="telefónne číslo na rodiča" rules={phoneRule} />
<FormCheckbox
Expand Down
Loading

0 comments on commit d27e21d

Please sign in to comment.