create useform hook
This commit is contained in:
7
frontend/app/(urls)/login/page.tsx
Normal file
7
frontend/app/(urls)/login/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
const page = () => {
|
||||
return <div>page</div>
|
||||
}
|
||||
|
||||
export default page
|
||||
7
frontend/app/(urls)/news/[slug]/page.tsx
Normal file
7
frontend/app/(urls)/news/[slug]/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
const page = () => {
|
||||
return <div>page</div>
|
||||
}
|
||||
|
||||
export default page
|
||||
7
frontend/app/(urls)/register/page.tsx
Normal file
7
frontend/app/(urls)/register/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
const page = () => {
|
||||
return <div>page</div>
|
||||
}
|
||||
|
||||
export default page
|
||||
0
frontend/app/api/auth/[...nextauth]/route.ts
Normal file
0
frontend/app/api/auth/[...nextauth]/route.ts
Normal file
168
frontend/app/hooks/useForm.ts
Normal file
168
frontend/app/hooks/useForm.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
'use client'
|
||||
|
||||
import { useState, ChangeEvent, FormEvent } from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { ValidationRules, ValidationErrors } from '../types'
|
||||
|
||||
type FormValue = string | number | boolean | string[] | number[]
|
||||
|
||||
type CustomChangeEvent = {
|
||||
target: {
|
||||
id: string
|
||||
value: FormValue
|
||||
type?: string
|
||||
checked?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export function useForm<T extends Record<string, FormValue>>(
|
||||
initialValues: T,
|
||||
validationRules?: { [K in keyof T]?: ValidationRules },
|
||||
onSubmit?: (values: T) => void
|
||||
) {
|
||||
const [values, setValues] = useState<T>(initialValues)
|
||||
const [errors, setErrors] = useState<ValidationErrors>({})
|
||||
const [isVisible, setIsVisible] = useState(false)
|
||||
|
||||
const handleChange = (
|
||||
e:
|
||||
| ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>
|
||||
| CustomChangeEvent
|
||||
) => {
|
||||
const { id, value, type } = e.target
|
||||
const isCheckbox = type === 'checkbox' && 'checked' in e.target
|
||||
|
||||
setValues((prev) => ({
|
||||
...prev,
|
||||
[id]: isCheckbox ? (e.target as HTMLInputElement).checked : value,
|
||||
}))
|
||||
|
||||
// скидываем ошибки
|
||||
if (errors[id]) {
|
||||
setErrors((prev) => {
|
||||
const newErrors = { ...prev }
|
||||
delete newErrors[id]
|
||||
return newErrors
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const resetField = (fieldName: keyof T, value: FormValue = '') => {
|
||||
setValues((prev) => ({
|
||||
...prev,
|
||||
[fieldName]: value,
|
||||
}))
|
||||
|
||||
// очищаем ошибки для этого поля, если они есть
|
||||
if (errors[fieldName as string]) {
|
||||
setErrors((prev) => {
|
||||
const newErrors = { ...prev }
|
||||
delete newErrors[fieldName as string]
|
||||
return newErrors
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const fieldNames: { [key: string]: string } = {
|
||||
name: 'Имя',
|
||||
surmame: 'Фамилия',
|
||||
email: 'Email',
|
||||
phone_number: 'Номер телефона',
|
||||
password: 'Пароль',
|
||||
privacy_policy: 'Политика конфиденциальности',
|
||||
}
|
||||
|
||||
const validate = () => {
|
||||
if (!validationRules) return true
|
||||
|
||||
const newErrors: ValidationErrors = {}
|
||||
|
||||
Object.keys(validationRules).forEach((key) => {
|
||||
const value = values[key]
|
||||
const rules = validationRules[key as keyof T]
|
||||
|
||||
if (rules?.required && !value) {
|
||||
newErrors[key] = 'Это поле обязательно'
|
||||
toast.error(
|
||||
`Поле "${fieldNames[key] || key}" обязательно для заполнения`,
|
||||
{
|
||||
duration: 2000,
|
||||
position: 'top-right',
|
||||
style: {
|
||||
background: '#FEE2E2',
|
||||
color: '#991B1B',
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
rules?.minLength &&
|
||||
typeof value === 'string' &&
|
||||
value.length < rules.minLength
|
||||
) {
|
||||
newErrors[key] = `Минимальная длина ${rules.minLength} символов`
|
||||
toast.error(
|
||||
`Минимальная длина поля "${fieldNames[key] || key}" - ${
|
||||
rules.minLength
|
||||
} символов`,
|
||||
{
|
||||
duration: 2000,
|
||||
position: 'top-right',
|
||||
style: {
|
||||
background: '#FEE2E2',
|
||||
color: '#991B1B',
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (
|
||||
rules?.pattern &&
|
||||
typeof value === 'string' &&
|
||||
!rules.pattern.test(value)
|
||||
) {
|
||||
newErrors[key] = 'Неверный формат'
|
||||
toast.error(`Поле "${fieldNames[key] || key}" заполнено некорректно`, {
|
||||
duration: 2000,
|
||||
position: 'top-right',
|
||||
style: {
|
||||
background: '#FEE2E2',
|
||||
color: '#991B1B',
|
||||
padding: '16px',
|
||||
borderRadius: '8px',
|
||||
},
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
setErrors(newErrors)
|
||||
return Object.keys(newErrors).length === 0
|
||||
}
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (validate() && onSubmit) {
|
||||
onSubmit(values)
|
||||
}
|
||||
}
|
||||
|
||||
const togglePasswordVisibility = () => {
|
||||
setIsVisible(!isVisible)
|
||||
}
|
||||
|
||||
return {
|
||||
values,
|
||||
errors,
|
||||
isVisible,
|
||||
setValues,
|
||||
handleChange,
|
||||
handleSubmit,
|
||||
togglePasswordVisibility,
|
||||
resetField,
|
||||
}
|
||||
}
|
||||
47
frontend/app/store/userStore.ts
Normal file
47
frontend/app/store/userStore.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { create } from 'zustand'
|
||||
import { persist } from 'zustand/middleware'
|
||||
import { User, UserState } from '../types'
|
||||
|
||||
interface UserStore extends UserState {
|
||||
// состояние
|
||||
isAuthenticated: boolean
|
||||
user: User | null
|
||||
|
||||
// действия
|
||||
setUser: (user: User | null) => void
|
||||
setAuthenticated: (isAuthenticated: boolean) => void
|
||||
logout: () => void
|
||||
|
||||
// асинхронные действия
|
||||
//! что пользователь может делать асинхронно?
|
||||
}
|
||||
|
||||
const useUserStore = create<UserStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
// начальное состояние
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
favorites: [],
|
||||
|
||||
// синхронные действия
|
||||
setUser: (user) => set({ user }),
|
||||
setAuthenticated: (isAuthenticated) => set({ isAuthenticated }),
|
||||
logout: () =>
|
||||
set({
|
||||
isAuthenticated: false,
|
||||
user: null,
|
||||
}),
|
||||
}),
|
||||
|
||||
//! асинхронщина?
|
||||
{ name: 'user-store' }
|
||||
)
|
||||
)
|
||||
|
||||
export default useUserStore
|
||||
|
||||
// пример использования
|
||||
// const { user, isAuthenticated } = useUserStore() -- получаем данные из стора
|
||||
// const { setUser, setAuthenticated } = useUserStore() -- устанавливаем данные в стор
|
||||
// const { logout } = useUserStore() -- выходим из пользовательского аккаунта
|
||||
@@ -1,4 +1,4 @@
|
||||
import Image, { StaticImageData } from 'next/image'
|
||||
import { StaticImageData } from 'next/image'
|
||||
|
||||
export interface TextInputProps {
|
||||
value: string
|
||||
@@ -16,7 +16,7 @@ export interface ButtonProps {
|
||||
onClick?: () => void
|
||||
className?: string
|
||||
text?: string
|
||||
type?: 'button'
|
||||
type?: 'button' | 'submit' | 'reset'
|
||||
}
|
||||
|
||||
export interface SearchCardProps {
|
||||
@@ -66,3 +66,46 @@ export interface NewsItem {
|
||||
export interface NewsProps {
|
||||
news: NewsItem[]
|
||||
}
|
||||
|
||||
export interface ValidationRules {
|
||||
required?: boolean
|
||||
minLength?: number
|
||||
pattern?: RegExp
|
||||
}
|
||||
|
||||
export interface ValidationErrors {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
export type ToastProps = {
|
||||
type: 'error' | 'success' | 'loading'
|
||||
message: string
|
||||
action?: {
|
||||
text: string
|
||||
onClick: () => void
|
||||
}
|
||||
duration?: number
|
||||
}
|
||||
|
||||
export type MembershipPlans = {
|
||||
plan: 'lite' | 'standart' | 'premium'
|
||||
}
|
||||
|
||||
export interface User {
|
||||
id?: number | undefined | string
|
||||
uuid?: string
|
||||
name: string
|
||||
surname: string
|
||||
image?: string
|
||||
phone_number?: string
|
||||
email: string
|
||||
country?: string
|
||||
city?: string
|
||||
plan: MembershipPlans
|
||||
account_type?: string // user или manager
|
||||
}
|
||||
|
||||
export interface UserState {
|
||||
isAuthenticated: boolean
|
||||
user: User | null
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user