forked from VinokurovVE/tests
routing and fetching data
This commit is contained in:
57
frontend_reactjs/package-lock.json
generated
57
frontend_reactjs/package-lock.json
generated
@ -12,7 +12,9 @@
|
|||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hook-form": "^7.51.5",
|
||||||
|
"react-router-dom": "^6.23.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
@ -680,6 +682,14 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@remix-run/router": {
|
||||||
|
"version": "1.16.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
|
||||||
|
"integrity": "sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||||
"version": "4.18.0",
|
"version": "4.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
|
||||||
@ -3158,6 +3168,51 @@
|
|||||||
"react": "^18.3.1"
|
"react": "^18.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-hook-form": {
|
||||||
|
"version": "7.51.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.5.tgz",
|
||||||
|
"integrity": "sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.22.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/react-hook-form"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17 || ^18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router": {
|
||||||
|
"version": "6.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.23.1.tgz",
|
||||||
|
"integrity": "sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.16.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-router-dom": {
|
||||||
|
"version": "6.23.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.23.1.tgz",
|
||||||
|
"integrity": "sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@remix-run/router": "1.16.1",
|
||||||
|
"react-router": "6.23.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/read-cache": {
|
"node_modules/read-cache": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
@ -14,7 +14,9 @@
|
|||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0",
|
||||||
|
"react-hook-form": "^7.51.5",
|
||||||
|
"react-router-dom": "^6.23.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
|
@ -1,24 +1,22 @@
|
|||||||
import { useState,useEffect } from 'react'
|
import { BrowserRouter as Router, Routes,Route} from "react-router-dom"
|
||||||
import Card from './components/Card'
|
import Users from "./pages/Users"
|
||||||
import axios from 'axios'
|
import Roles from "./pages/Roles"
|
||||||
interface ICard{
|
import Main from "./pages/Main"
|
||||||
firstname: string
|
import NotFound from "./pages/NotFound"
|
||||||
lastname: string
|
import Navigate from "./components/Navigate"
|
||||||
email: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
// const data = [
|
|
||||||
// {firstname:"Иван",lastname:"Петров", email:"email@test.ru"},
|
|
||||||
// {firstname:"Алексей",lastname:"Петров", email:"email2@test.ru"},
|
|
||||||
// {firstname:"Василиса",lastname:"Петрова", email:"email3@test.ru"}
|
|
||||||
// ]
|
|
||||||
const [cards, setCards] = useState<ICard[]>([]);
|
|
||||||
useEffect(()=>{axios.get("http://localhost:8000/auth/user/").then((res) =>{setCards(res.data as ICard[]);})},[])
|
|
||||||
console.log(cards)
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{cards.map((card, index) => <Card key={index} {...card}/>)}
|
<Navigate />
|
||||||
|
<Router>
|
||||||
|
<Routes>
|
||||||
|
<Route index path="/" element={ <Main />} />
|
||||||
|
<Route path="/user" element={ <Users />} />
|
||||||
|
<Route path="/role" element={ <Roles />} />
|
||||||
|
<Route path="*" element={<NotFound />}/>
|
||||||
|
</Routes>
|
||||||
|
</Router>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
20
frontend_reactjs/src/components/FetchingData.ts
Normal file
20
frontend_reactjs/src/components/FetchingData.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
export function useDataFetching<T> (url:string, initData:T):T{
|
||||||
|
const [data, setData] = useState<T>(initData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
const response = await fetch(url);
|
||||||
|
const result = await response.json();
|
||||||
|
setData(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, [url]);
|
||||||
|
console.log(data)
|
||||||
|
// Memoize the data value
|
||||||
|
const memoizedData = useMemo<T>(() => data, [data]);
|
||||||
|
return memoizedData;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useDataFetching;
|
57
frontend_reactjs/src/components/Modal.tsx
Normal file
57
frontend_reactjs/src/components/Modal.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
export interface Props {
|
||||||
|
showModal: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Modal(props:Props){
|
||||||
|
return(<>
|
||||||
|
{props.showModal &&
|
||||||
|
<div id="crud-modal" tabIndex={-1} className="bg-black opacity-75 overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full" onClick={()=> console.log("click")}>
|
||||||
|
<div className="relative p-4 w-full max-w-md max-h-full" >
|
||||||
|
<div className="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
|
<div className="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
|
||||||
|
Create New Product
|
||||||
|
</h3>
|
||||||
|
<button type="button" className="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-toggle="crud-modal">
|
||||||
|
<svg className="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||||
|
<path stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
||||||
|
</svg>
|
||||||
|
<span className="sr-only">Close modal</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<form className="p-4 md:p-5">
|
||||||
|
<div className="grid gap-4 mb-4 grid-cols-2">
|
||||||
|
<div className="col-span-2">
|
||||||
|
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Name</label>
|
||||||
|
<input type="text" name="name" id="name" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder="Type product name" />
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2 sm:col-span-1">
|
||||||
|
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Price</label>
|
||||||
|
<input type="number" name="price" id="price" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder="$2999" />
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2 sm:col-span-1">
|
||||||
|
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Category</label>
|
||||||
|
<select id="category" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-500 focus:border-primary-500 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500">
|
||||||
|
<option >Select category</option>
|
||||||
|
<option value="TV">TV/Monitors</option>
|
||||||
|
<option value="PC">PC</option>
|
||||||
|
<option value="GA">Gaming/Console</option>
|
||||||
|
<option value="PH">Phones</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="col-span-2">
|
||||||
|
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Product Description</label>
|
||||||
|
<textarea id="description" rows={4} className="block p-2.5 w-full text-sm text-gray-900 bg-gray-50 rounded-lg border border-gray-300 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="Write product description here"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="submit" className="text-white inline-flex items-center bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">
|
||||||
|
<svg className="me-1 -ms-1 w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clipRule="evenodd"></path></svg>
|
||||||
|
Add new product
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> }
|
||||||
|
|
||||||
|
</>)
|
||||||
|
}
|
21
frontend_reactjs/src/components/Navigate.tsx
Normal file
21
frontend_reactjs/src/components/Navigate.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export default function Navigate(){
|
||||||
|
return(
|
||||||
|
<>
|
||||||
|
<nav className="bg-white border-gray-200 dark:bg-gray-900">
|
||||||
|
<div className="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
|
||||||
|
<ul className="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
|
||||||
|
<li>
|
||||||
|
<a href="/" className="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Главная</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/user" className="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Пользователи</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/role" className="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent">Роли</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
19
frontend_reactjs/src/components/RoleCard.tsx
Normal file
19
frontend_reactjs/src/components/RoleCard.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useState, useEffect } from 'react'
|
||||||
|
|
||||||
|
interface IRoleCard{
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function RoleCard(props:IRoleCard) {
|
||||||
|
const [curr_card, setCard] = useState<IRoleCard>({id: 1, name: "Тест"});
|
||||||
|
useEffect(()=>{setCard(props);},[curr_card])
|
||||||
|
return (
|
||||||
|
<div className='block p-6 max-w-sm bg-gray-300
|
||||||
|
rounded-lg border border-gray-500 align-items: center;'>
|
||||||
|
<div className='text-4xl text-center'>{curr_card.name}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RoleCard
|
3
frontend_reactjs/src/pages/Main.tsx
Normal file
3
frontend_reactjs/src/pages/Main.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function Main (){
|
||||||
|
return ( <><h1>Main page</h1></>)
|
||||||
|
}
|
3
frontend_reactjs/src/pages/NotFound.tsx
Normal file
3
frontend_reactjs/src/pages/NotFound.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function NotFound (){
|
||||||
|
return ( <><h1>Page not found</h1></>)
|
||||||
|
}
|
26
frontend_reactjs/src/pages/Roles.tsx
Normal file
26
frontend_reactjs/src/pages/Roles.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useState} from 'react'
|
||||||
|
import RoleCard from '../components/RoleCard'
|
||||||
|
import Modal from '../components/Modal'
|
||||||
|
import useDataFetching from '../components/FetchingData'
|
||||||
|
|
||||||
|
interface IRoleCard{
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
interface Props {
|
||||||
|
showModal: boolean;
|
||||||
|
}
|
||||||
|
function Users() {
|
||||||
|
const [showModal, setShowModal] = useState<Props>({showModal: false});
|
||||||
|
const cards = useDataFetching<IRoleCard[]>("http://localhost:8000/auth/role/",[])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{cards.map((card, index) => <RoleCard key={index} {...card}/>)}
|
||||||
|
<button className='absolute w-0 h-0' onClick={() => setShowModal({showModal:true})}>+</button>
|
||||||
|
<Modal {...showModal}/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Users
|
18
frontend_reactjs/src/pages/Users.tsx
Normal file
18
frontend_reactjs/src/pages/Users.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import Card from '../components/Card'
|
||||||
|
import useDataFetching from '../components/FetchingData'
|
||||||
|
interface ICard{
|
||||||
|
firstname: string
|
||||||
|
lastname: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function Users() {
|
||||||
|
const cards= useDataFetching<ICard[]>("http://localhost:8000/auth/user/",[])
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{cards.map((card, index) => <Card key={index} {...card}/>)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Users
|
1734
frontend_reactjs/yarn.lock
Normal file
1734
frontend_reactjs/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user