|
@@ -0,0 +1,278 @@
|
|
|
+import {useState, useEffect, useRef} from 'react';
|
|
|
+import './App.scss';
|
|
|
+
|
|
|
+/////////////Spoiler
|
|
|
+const Spoiler = ({ header = "+", open, children }) => {
|
|
|
+ const [opening, setOpening] = useState('');
|
|
|
+ if (opening !== '') {
|
|
|
+ open = opening;
|
|
|
+ }
|
|
|
+ return <>
|
|
|
+ <div onClick={() => setOpening(!open)}>{header}</div>
|
|
|
+ <div>{open && children}</div>
|
|
|
+ </>
|
|
|
+}
|
|
|
+
|
|
|
+/////////////RangeInput
|
|
|
+const RangeInput = ({min,max}) => {
|
|
|
+
|
|
|
+ const [text, setText] = useState('')
|
|
|
+ return <div><input
|
|
|
+ style={{backgroundColor: text.length<min || text.length>max ?'red':'white'}}
|
|
|
+ value={text}
|
|
|
+ onChange={e => setText(e.target.value)}
|
|
|
+ type="text"/></div>
|
|
|
+}
|
|
|
+
|
|
|
+////////////LoginForm
|
|
|
+const LoginForm = ({onLogin}) => {
|
|
|
+ const [login, setTextLogin] = useState('')
|
|
|
+ const [password, setTextPass] = useState('')
|
|
|
+ let buttonDisabled= login.length<6 || password.length<6 ? true : false
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <input placeholder='Login'
|
|
|
+ value={login}
|
|
|
+ onChange={e => setTextLogin(e.target.value)}
|
|
|
+ type="text"
|
|
|
+ /><br/>
|
|
|
+ <input placeholder='Password'
|
|
|
+ value={password}
|
|
|
+ onChange={e => setTextPass(e.target.value)}
|
|
|
+ type="password"
|
|
|
+ /><br/>
|
|
|
+ <button
|
|
|
+ disabled= {buttonDisabled}
|
|
|
+ onClick={() => onLogin({login,password})}
|
|
|
+ >Login...</button>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+///////////PasswordConfirm
|
|
|
+const PasswordConfirm = ({min}) => {
|
|
|
+
|
|
|
+ const [pass1, setPass1] = useState('')
|
|
|
+ const [pass2, setPass2] = useState('')
|
|
|
+ let inpunStyle
|
|
|
+ let errorText
|
|
|
+ let errorText2
|
|
|
+ if(pass1===pass2 && pass1.length>=min && pass2.length>=min){
|
|
|
+ inpunStyle = 'green'
|
|
|
+ errorText =''
|
|
|
+ errorText2 =''
|
|
|
+ }else{
|
|
|
+ pass1!==pass2? errorText = 'Пароли не совпадают':errorText =''
|
|
|
+ pass1.length<min && pass2.length<min? errorText2 = `Пароль должени состоять минимум из ${min} символов`:errorText2 =''
|
|
|
+ inpunStyle = 'white'
|
|
|
+ }
|
|
|
+ return <div>Введите пароль<br/>
|
|
|
+ <input
|
|
|
+ style={{backgroundColor: inpunStyle}}
|
|
|
+ value={pass1}
|
|
|
+ onChange={e => setPass1(e.target.value)}
|
|
|
+ placeholder='Password'
|
|
|
+ type="password"
|
|
|
+ /><br/>Повторите пароль<br/>
|
|
|
+ <input
|
|
|
+ style={{backgroundColor: inpunStyle}}
|
|
|
+ value={pass2}
|
|
|
+ onChange={e => setPass2(e.target.value)}
|
|
|
+ placeholder='Password'
|
|
|
+ type="password"
|
|
|
+ /><div style={{color:'red'}}>{errorText}<br/>{errorText2}</div>
|
|
|
+ </div>
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const oneCat = {
|
|
|
+ "_id": "62c9472cb74e1f5f2ec1a0d4",
|
|
|
+ "name": "iPhone",
|
|
|
+ "goods": [
|
|
|
+ {
|
|
|
+ "_id": "62c9472cb74e1f5f2ec1a0d1",
|
|
|
+ "name": "iPhone 4",
|
|
|
+ "images": [
|
|
|
+ {
|
|
|
+ "url": "images/e48e7ee1bcc4ab5432d1e7a3a89b8466"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "url": "images/58c6157d51d8c2430c4dd41b6d0132f4"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "_id": "62c9472cb74e1f5f2ec1a0d2",
|
|
|
+ "name": "iPhone X",
|
|
|
+ "images": [
|
|
|
+ {
|
|
|
+ "url": "images/c67956dff69d1160a6e70b71838d7282"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "url": "images/0153956fc7bf99567e620ee446319b00"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "_id": "62c9472cb74e1f5f2ec1a0d3",
|
|
|
+ "name": "iPhone 13",
|
|
|
+ "images": [
|
|
|
+ {
|
|
|
+ "url": "images/56c5d476685355221b1a3ba2c554ad91"
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "url": "images/29393a087c933d7caea010c98f4d2876"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "_id": "634190ccb74e1f5f2ec1a37b",
|
|
|
+ "name": "Iphone14",
|
|
|
+ "images": [
|
|
|
+ {
|
|
|
+ "url": "images/2b2b08dfca28972c19f3f901c68966af"
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+}
|
|
|
+const origUrl = 'http://shop-roles.node.ed.asmer.org.ua/'
|
|
|
+
|
|
|
+const GoodCard = ({good}) => {
|
|
|
+ const [indexArr, setindexArr] = useState(0)
|
|
|
+ return <div>
|
|
|
+ {good.name}<br/>
|
|
|
+ <img
|
|
|
+ onClick={()=>indexArr=== good.images.length-1?setindexArr(0) :setindexArr(indexArr+1)}/////////////
|
|
|
+ src={origUrl+good.images[indexArr].url}/>
|
|
|
+ </div>
|
|
|
+}
|
|
|
+
|
|
|
+const OneCat = ({cat=oneCat}) =>
|
|
|
+<div>
|
|
|
+ <h1>{cat.name}</h1>
|
|
|
+ <div>{cat.goods.map(el=><GoodCard good={el}/>)}</div>
|
|
|
+</div>
|
|
|
+
|
|
|
+
|
|
|
+///////////////Timer
|
|
|
+const Timer = ({countSeconds}) => {
|
|
|
+ const [time, setTime] = useState(countSeconds)
|
|
|
+ const [startTimer, setStartTimer] = useState(false)
|
|
|
+ const [timerId, setTimerId] = useState(0)
|
|
|
+ useEffect(() => {
|
|
|
+ let intervalId = null
|
|
|
+ if(startTimer){
|
|
|
+ intervalId = setInterval(() => setTime(count => count -1), 1000)
|
|
|
+ setTimerId(intervalId)
|
|
|
+ }else{
|
|
|
+ clearInterval(timerId)
|
|
|
+ }
|
|
|
+ },[startTimer])
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ if(time===0){
|
|
|
+ clearInterval(timerId)
|
|
|
+ }
|
|
|
+ },[time===0])
|
|
|
+
|
|
|
+ let hours = Math.floor(time/60/60)
|
|
|
+ let minutes = Math.floor(time/60)-(hours*60)
|
|
|
+ let sec = time % 60
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <div>{hours}:{minutes}:{sec}</div>
|
|
|
+ <button onClick={()=>setStartTimer(true) }>Start</button>
|
|
|
+ <button onClick={()=>setStartTimer(false) }>Stop</button>
|
|
|
+ </>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+const TimerControl = ()=>{
|
|
|
+ const [hours, setHours] = useState('')
|
|
|
+ const [minutes, setMinutes] = useState('')
|
|
|
+ const [seconds, setSeconds] = useState('')
|
|
|
+ const [start, setStart] = useState(false)
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <input
|
|
|
+ className="inputTime"
|
|
|
+ value={hours}
|
|
|
+ onChange={e => setHours(e.target.value)}
|
|
|
+ type="number"
|
|
|
+ placeholder='hours'></input>
|
|
|
+ <input
|
|
|
+ className="inputTime"
|
|
|
+ value={minutes}
|
|
|
+ onChange={e => setMinutes(e.target.value)}
|
|
|
+ type="number"
|
|
|
+ placeholder='minutes'></input>
|
|
|
+ <input
|
|
|
+ className="inputTime"
|
|
|
+ value={seconds}
|
|
|
+ onChange={e => setSeconds(e.target.value)}
|
|
|
+ type="number"
|
|
|
+ placeholder='seconds'></input><br/>
|
|
|
+ <button onClick={() => setStart(!start)}>Set time</button>
|
|
|
+ {start && <Timer countSeconds={hours*60*6 + minutes*6 + seconds}/>}
|
|
|
+ </>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const TimerContainer = ({seconds, refresh, render}) => {
|
|
|
+ const [sec, setSec] = useState(seconds);
|
|
|
+ useEffect(() => {
|
|
|
+ if(sec > 0){
|
|
|
+ const intervalId = setInterval(()=>{setSec(sec=> sec-1)},refresh)
|
|
|
+ return () => {
|
|
|
+ clearInterval(intervalId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },[sec]);
|
|
|
+ const Render = render;
|
|
|
+ return (<Render seconds={sec} />)
|
|
|
+}
|
|
|
+const SecondsTimer = ({seconds}) => <h2>{seconds}</h2>
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+function App() {
|
|
|
+ return (
|
|
|
+ <div className="App">
|
|
|
+ <TimerContainer seconds={1800} refresh={1000} render={Watch}/>
|
|
|
+ <TimerContainer seconds={1800} refresh={100} render={SecondsTimer}/>
|
|
|
+ <TimerControl/>
|
|
|
+
|
|
|
+
|
|
|
+ <Spoiler header={<h1>Заголовок</h1>} open>
|
|
|
+ Контент 1
|
|
|
+ <p>
|
|
|
+ лорем ипсум траливали и тп.
|
|
|
+ </p>
|
|
|
+ </Spoiler>
|
|
|
+ <Spoiler>
|
|
|
+ <h2>Контент 2</h2>
|
|
|
+ <p>
|
|
|
+ лорем ипсум траливали и тп.
|
|
|
+ </p>
|
|
|
+ </Spoiler>
|
|
|
+ <div>Задание RangeInput</div>
|
|
|
+ <RangeInput min={2} max={10} />
|
|
|
+ <div>Задание LoginForm</div>
|
|
|
+ <LoginForm onLogin={({login, password}) => console.log('ЛОГИН И ПАРОЛЬ', login, password)}/>
|
|
|
+ <div>Задание PasswordConfirm </div>
|
|
|
+ <PasswordConfirm min={6} />
|
|
|
+ <OneCat/>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+export default App;
|