create base components

This commit is contained in:
Timofey
2025-08-29 14:01:39 +03:00
parent 691315da0d
commit ffe77d1ce0
15 changed files with 1577 additions and 160 deletions

View File

@@ -6,12 +6,15 @@ name = "pypi"
[packages] [packages]
django = "*" django = "*"
djangorestframework = "*" djangorestframework = "*"
pycodestyle = "*"
django-cors-headers = "*" django-cors-headers = "*"
psycopg2-binary = "*" psycopg2-binary = "*"
dotenv = "*" dotenv = "*"
pytest = "*" pytest = "*"
pytest-django = "*" pytest-django = "*"
djangorestframework-simplejwt = "*"
pycodestyle = "*"
requests = "*"
pyjwt = "*"
[dev-packages] [dev-packages]

138
backend/Pipfile.lock generated
View File

@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "e150d0e5553cb65551da7428eb7dd6810099cf0188118c2031d785f562315b37" "sha256": "952a50ce3f529481ea1b021ac88ac52462e1b6b7ace31e1f03125e6eca1b281e"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -24,6 +24,99 @@
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==3.9.1" "version": "==3.9.1"
}, },
"certifi": {
"hashes": [
"sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407",
"sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"
],
"markers": "python_version >= '3.7'",
"version": "==2025.8.3"
},
"charset-normalizer": {
"hashes": [
"sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91",
"sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0",
"sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154",
"sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601",
"sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884",
"sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07",
"sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c",
"sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64",
"sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe",
"sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f",
"sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432",
"sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc",
"sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa",
"sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9",
"sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae",
"sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19",
"sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d",
"sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e",
"sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4",
"sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7",
"sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312",
"sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92",
"sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31",
"sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c",
"sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f",
"sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99",
"sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b",
"sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15",
"sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392",
"sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f",
"sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8",
"sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491",
"sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0",
"sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc",
"sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0",
"sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f",
"sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a",
"sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40",
"sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927",
"sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849",
"sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce",
"sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14",
"sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05",
"sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c",
"sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c",
"sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a",
"sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc",
"sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34",
"sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9",
"sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096",
"sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14",
"sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30",
"sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b",
"sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b",
"sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942",
"sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db",
"sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5",
"sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b",
"sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce",
"sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669",
"sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0",
"sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018",
"sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93",
"sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe",
"sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049",
"sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a",
"sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef",
"sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2",
"sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca",
"sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16",
"sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f",
"sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb",
"sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1",
"sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557",
"sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37",
"sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7",
"sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72",
"sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c",
"sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"
],
"markers": "python_version >= '3.7'",
"version": "==3.4.3"
},
"django": { "django": {
"hashes": [ "hashes": [
"sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae", "sha256:0745b25681b129a77aae3d4f6549b62d3913d74407831abaa0d9021a03954bae",
@@ -51,6 +144,15 @@
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==3.16.1" "version": "==3.16.1"
}, },
"djangorestframework-simplejwt": {
"hashes": [
"sha256:2c30f3707053d384e9f315d11c2daccfcb548d4faa453111ca19a542b732e469",
"sha256:e72c5572f51d7803021288e2057afcbd03f17fe11d484096f40a460abc76e87f"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==5.5.1"
},
"dotenv": { "dotenv": {
"hashes": [ "hashes": [
"sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9" "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9"
@@ -58,6 +160,14 @@
"index": "pypi", "index": "pypi",
"version": "==0.9.9" "version": "==0.9.9"
}, },
"idna": {
"hashes": [
"sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9",
"sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"
],
"markers": "python_version >= '3.6'",
"version": "==3.10"
},
"iniconfig": { "iniconfig": {
"hashes": [ "hashes": [
"sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7",
@@ -174,6 +284,15 @@
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==2.19.2" "version": "==2.19.2"
}, },
"pyjwt": {
"hashes": [
"sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953",
"sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==2.10.1"
},
"pytest": { "pytest": {
"hashes": [ "hashes": [
"sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7",
@@ -200,6 +319,15 @@
"markers": "python_version >= '3.9'", "markers": "python_version >= '3.9'",
"version": "==1.1.1" "version": "==1.1.1"
}, },
"requests": {
"hashes": [
"sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6",
"sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf"
],
"index": "pypi",
"markers": "python_version >= '3.9'",
"version": "==2.32.5"
},
"sqlparse": { "sqlparse": {
"hashes": [ "hashes": [
"sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272",
@@ -207,6 +335,14 @@
], ],
"markers": "python_version >= '3.8'", "markers": "python_version >= '3.8'",
"version": "==0.5.3" "version": "==0.5.3"
},
"urllib3": {
"hashes": [
"sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760",
"sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"
],
"markers": "python_version >= '3.9'",
"version": "==2.5.0"
} }
}, },
"develop": {} "develop": {}

View File

@@ -1,15 +1,22 @@
asgiref==3.9.1 asgiref==3.9.1
certifi==2025.8.3
charset-normalizer==3.4.3
Django==5.2.5 Django==5.2.5
django-cors-headers==4.7.0 django-cors-headers==4.7.0
djangorestframework==3.16.1 djangorestframework==3.16.1
djangorestframework_simplejwt==5.5.1
dotenv==0.9.9 dotenv==0.9.9
idna==3.10
iniconfig==2.1.0 iniconfig==2.1.0
packaging==25.0 packaging==25.0
pluggy==1.6.0 pluggy==1.6.0
psycopg2-binary==2.9.10 psycopg2-binary==2.9.10
pycodestyle==2.14.0 pycodestyle==2.14.0
Pygments==2.19.2 Pygments==2.19.2
PyJWT==2.10.1
pytest==8.4.1 pytest==8.4.1
pytest-django==4.11.1 pytest-django==4.11.1
python-dotenv==1.1.1 python-dotenv==1.1.1
requests==2.32.5
sqlparse==0.5.3 sqlparse==0.5.3
urllib3==2.5.0

9
frontend/.prettierignore Normal file
View File

@@ -0,0 +1,9 @@
node_modules
.next
dist
out
coverage
public
.vscode
*.log
*.lock

9
frontend/.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "avoid",
"plugins": ["prettier-plugin-tailwindcss"]
}

View File

@@ -0,0 +1,7 @@
import React from 'react'
const LoginPage = () => {
return <div> LoginPage</div>
}
export default LoginPage

View File

@@ -0,0 +1,9 @@
import React from 'react'
const page = () => {
return (
<div>page</div>
)
}
export default page

View File

@@ -0,0 +1,9 @@
import React from 'react'
const page = () => {
return (
<div>page</div>
)
}
export default page

View File

@@ -0,0 +1,131 @@
import { useQuery, useMutation, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query'
import axios, { AxiosError, AxiosRequestConfig } from 'axios'
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
interface FetchOptions<TData, TVariables, TError> {
method?: HttpMethod
config?: AxiosRequestConfig
queryOptions?: Omit<UseQueryOptions<TData, TError>, 'queryKey' | 'queryFn'>
mutationOptions?: Omit<UseMutationOptions<TData, TError, TVariables>, 'mutationFn'>
}
interface QueryResult<TData, TError> {
data: TData | undefined
isLoading: boolean
error: TError | null
refetch: () => Promise<unknown>
}
interface MutationResult<TData, TVariables, TError> {
mutate: (variables: TVariables) => void
isLoading: boolean
error: TError | null
data: TData | undefined
}
export function useClientFetch<TData = unknown, TVariables = void, TError = AxiosError>(
url: string,
options: FetchOptions<TData, TVariables, TError> = {}
): TVariables extends void
? QueryResult<TData, TError>
: MutationResult<TData, TVariables, TError> {
const { method = 'GET', config = {}, queryOptions = {}, mutationOptions = {} } = options
const API_URL = process.env.NEXT_PUBLIC_API_URL
const fullUrl = url.startsWith('http') ? url : `${API_URL}${url}`
// всегда вызываем оба хука
const query = useQuery<TData, TError>({
queryKey: [url, config.params],
queryFn: async () => {
const response = await axios.get(fullUrl, config)
return response.data
},
...queryOptions,
// отключаем автоматическое выполнение для мутаций
enabled: method === 'GET' && queryOptions.enabled !== false,
})
const mutation = useMutation<TData, TError, TVariables>({
mutationFn: async (variables: TVariables) => {
const response = await axios({
method: method.toLowerCase(),
url: fullUrl,
data: variables,
...config,
})
return response.data
},
...mutationOptions,
})
// возвращаем соответствующий результат в зависимости от метода
if (method === 'GET') {
return {
data: query.data,
isLoading: query.isLoading,
error: query.error,
refetch: query.refetch,
} as TVariables extends void ? QueryResult<TData, TError> : never
}
return {
mutate: mutation.mutate,
isLoading: mutation.isPending,
error: mutation.error,
data: mutation.data,
} as TVariables extends void ? never : MutationResult<TData, TVariables, TError>
}
// примеры использования:
/*
// GET запрос
interface UserData {
id: number
name: string
email: string
}
const { data, isLoading, error } = useClientFetch<UserData>('/users/me')
// POST запрос с типизированным payload
interface LoginPayload {
email: string
password: string
}
interface LoginResponse {
token: string
user: UserData
}
const { mutate, isLoading } = useClientFetch<LoginResponse, LoginPayload>('/auth/login', {
method: 'POST',
mutationOptions: {
onSuccess: (data) => {
// data типизирован как LoginResponse
},
onError: (error) => {
// error типизирован как AxiosError
}
}
})
// использование:
mutate({ email: 'user@example.com', password: '123456' })
// PATCH запрос
interface UpdateUserPayload {
name?: string
email?: string
}
const { mutate } = useClientFetch<UserData, UpdateUserPayload>('/users/me', {
method: 'PATCH'
})
// использование:
mutate({ name: 'New Name' })
*/

View File

@@ -0,0 +1,150 @@
'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[] | null
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 } = {
login: 'Логин',
password: 'Пароль',
accountType: 'Роль',
}
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,
}
}

View File

@@ -0,0 +1,9 @@
export interface ValidationRules {
required?: boolean
minLength?: number
pattern?: RegExp
}
export interface ValidationErrors {
[key: string]: string
}

View File

@@ -0,0 +1,9 @@
import React from 'react'
const Selector = () => {
return (
<div>Selector</div>
)
}
export default Selector

View File

@@ -0,0 +1,9 @@
import React from 'react'
const TextInput = () => {
return (
<div>TextInput</div>
)
}
export default TextInput

File diff suppressed because it is too large Load Diff

View File

@@ -11,9 +11,15 @@
"dependencies": { "dependencies": {
"@babylonjs/core": "^6.44.0", "@babylonjs/core": "^6.44.0",
"@babylonjs/loaders": "^6.49.0", "@babylonjs/loaders": "^6.49.0",
"@tanstack/react-query": "^5.85.5",
"axios": "^1.11.0",
"next": "^15.4.3", "next": "^15.4.3",
"next-auth": "^4.24.11",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0" "react-dom": "19.1.0",
"react-hot-toast": "^2.6.0",
"react-select": "^5.10.2",
"zustand": "^5.0.8"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
@@ -23,6 +29,8 @@
"@types/react-dom": "^19", "@types/react-dom": "^19",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.4.3", "eslint-config-next": "15.4.3",
"prettier": "^3.6.2",
"prettier-plugin-tailwindcss": "^0.6.14",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }