diff --git a/wearright/public/index.html b/wearright/public/index.html index 75980d58e22b9e36512631b8f786a5ef31221c14..fe04a249681662477ee3de34e7aab54462e97706 100644 --- a/wearright/public/index.html +++ b/wearright/public/index.html @@ -3,6 +3,7 @@ <head> <meta charset="utf-8" /> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" /> + <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" diff --git a/wearright/src/App.js b/wearright/src/App.js index af74d877e2f4d1fedfdf51cc80de05715479332b..8e89fdb3ccae7abb28227a19e4ba63ceb37d354a 100644 --- a/wearright/src/App.js +++ b/wearright/src/App.js @@ -1,34 +1,32 @@ import React, { Component } from 'react'; import './App.css'; -import Start from "../src/components/Start.js" -import Profile from "../src/components/Profile.js" import Cookies from 'universal-cookie'; -import MainPage from "../src/components/MainPage.js" +import Start from "../src/components/bootstrap/Start.js"; +import MainCont from "../src/components/bootstrap/MainCont"; class App extends Component { + constructor(props) { + super(props); - render() { - const cookies = new Cookies(); - //const c = cookies.get('firstPage') - //console.log("main "+ c) + const c = new Cookies(); - //if cookie exists, load main view - if(cookies.get('firstPage') === "true"){ - return ( - <div className="App"> - <MainPage /> - </div> - ); + this.state = { + firstPage: c.get('firstPage') === "true" ? true : false + } } - else{ - //If cookie not found: show start view - return ( - <div className="App"> - <Start /> - </div> - ); + + render() { + const { firstPage } = this.state; + + if (!firstPage) + return( + <Start /> + ) + else + return ( + <MainCont /> + ) } - } } export default App; diff --git a/wearright/src/components/bootstrap/Clothes.js b/wearright/src/components/bootstrap/Clothes.js new file mode 100644 index 0000000000000000000000000000000000000000..da967625fe7c152ec72b17c9b2bd163ed6d63719 --- /dev/null +++ b/wearright/src/components/bootstrap/Clothes.js @@ -0,0 +1,30 @@ +import React, { Component } from 'react'; + +import { Row, Col } from 'react-bootstrap'; + +class Clothes extends Component { + render() { + const { clothing } = this.props; + + let cList = + <ul> + Lataa... + </ul> + + if (clothing) + cList = + <ul> + {clothing.map((t, i) => <li key={i}>{t.Vaatenimi}</li>)} + </ul> + + return ( + <Row> + <Col> + <h2>Sinun kannattaisi pukea jotain seuraavista: </h2> + {cList} + </Col> + </Row> + ) + } +} +export default Clothes; \ No newline at end of file diff --git a/wearright/src/components/bootstrap/MainCont.js b/wearright/src/components/bootstrap/MainCont.js new file mode 100644 index 0000000000000000000000000000000000000000..237da3a3ee6eb874f0acf03386e966d1af0012e2 --- /dev/null +++ b/wearright/src/components/bootstrap/MainCont.js @@ -0,0 +1,64 @@ +import React, { Component } from 'react'; +import Cookies from 'universal-cookie'; + +import Menu from './Menu'; + +import Home from './Home'; + +class MainCont extends Component { + constructor(props) { + super(props); + + const c = new Cookies(); + const profiles = c.get("profile"); + + this.state = { + profiles, + selectedProfile: profiles[0], + view: "home" + } + + this.handleMenuClick = this.handleMenuClick.bind(this); + } + + handleMenuClick(e) { + this.setState({ + view: e + }); + } + + render() { + const { profiles, selectedProfile, view } = this.state; + + // Decide what view to show + let shownView = null; + if (view === "home") + shownView = + <Home + profiles={profiles} + currentSelectedProfile={selectedProfile} + /> + else if (view === "plan") + shownView = + <h1> + TODO Suunnitele Matka + </h1> + else if (view === "edit") + shownView = + <h1> + TODO Profiilin muokkaus + </h1> + + return ( + <div> + <Menu + handleMenuClick={this.handleMenuClick} + /> + <div className="container"> + {shownView} + </div> + </div> + ) + } +} +export default MainCont; \ No newline at end of file diff --git a/wearright/src/components/bootstrap/Menu.js b/wearright/src/components/bootstrap/Menu.js new file mode 100644 index 0000000000000000000000000000000000000000..d6cdb2fa0972e9087eb771eb5f07a7914f282a09 --- /dev/null +++ b/wearright/src/components/bootstrap/Menu.js @@ -0,0 +1,25 @@ +import React, { Component } from 'react'; +import { Navbar, Nav } from 'react-bootstrap' + + +class Menu extends Component { + render() { + const { handleMenuClick } = this.props; + + return ( + <Navbar bg="light" expand="lg"> + <Navbar.Brand>Wear Right</Navbar.Brand> + <Navbar.Toggle aria-controls="basic-navbar-nav" /> + + <Navbar.Collapse id="basic-navbar-nav"> + <Nav className="mr-auto"> + <Nav.Link onClick={e => handleMenuClick("home")} href="#">Päänäkymä</Nav.Link> + <Nav.Link onClick={e => handleMenuClick("plan")} href="#">Suunnittele Matka</Nav.Link> + <Nav.Link onClick={e => handleMenuClick("edit")} href="#">Muokkaa Profiilia</Nav.Link> + </Nav> + </Navbar.Collapse> + </Navbar> + ) + } +} +export default Menu; \ No newline at end of file diff --git a/wearright/src/components/bootstrap/ProfileForm.js b/wearright/src/components/bootstrap/ProfileForm.js new file mode 100644 index 0000000000000000000000000000000000000000..6927fe76813c5afc797b56bbd1697c7a814213e2 --- /dev/null +++ b/wearright/src/components/bootstrap/ProfileForm.js @@ -0,0 +1,101 @@ +import React, { Component } from 'react'; + +// Bootstrap +import { Form, Button } from 'react-bootstrap'; + +class ProfileForm extends Component { + constructor(props) { + super(props); + + this.state = { + cities: [], + validated: false + } + + this.generateAgeRange = this.generateAgeRange.bind(this); + this.getCities = this.getCities.bind(this); + } + + generateAgeRange() { + const tempArr = []; + + for (let i = 6; i < 101; i++) { + tempArr.push(<option key={i}>{i}</option>); + } + + return tempArr; + } + + getCities() { + console.log("uwu"); + const tempArr = []; + + // Don't allow for multiple gets + if (this.state.cities.length === 0) { + const proxyUrl = "https://cors-anywhere.herokuapp.com/"; + + fetch(proxyUrl + "http://207.154.210.234/get/cities") + .then(res => res.json()) + .then(result => { + for (let i = 0; i < result.length; i++) { + tempArr.push(<option key={i}>{result[i]}</option>); + } + + console.log(tempArr); + this.setState({ cities: tempArr }); + }); + } + } + + componentDidMount() { + this.getCities(); + } + + render() { + const { cities } = this.state; + const { profileOnSubmit } = this.props; + + return ( + <Form onSubmit={ profileOnSubmit }> + <Form.Group controlId="profileForm.userName"> + <Form.Label>Nimimerkki</Form.Label> + <Form.Control required name="username" type="text"></Form.Control> + <Form.Control.Feedback type="invalid"> + Käyttäjänimi on vaadittu + </Form.Control.Feedback> + </Form.Group> + + <Form.Group controlId="profileForm.age"> + <Form.Label>Ikä</Form.Label> + <Form.Control required name="age" as="select"> + { this.generateAgeRange() } + </Form.Control> + </Form.Group> + + <Form.Group controlId="profileForm.sex"> + <Form.Label>Sukupuoli</Form.Label> + <Form.Control required name="sex" as="select"> + <option>Mies</option> + <option>Nainen</option> + </Form.Control> + </Form.Group> + + <Form.Group controlId="profileForm.city"> + <Form.Label>Kaupunki</Form.Label> + <Form.Control name="city" as="select"> + <option hidden>Valitse Kaupunki</option> + {cities} + </Form.Control> + <Form.Control.Feedback type="invalid"> + Kaupunki on vaadittu! + </Form.Control.Feedback> + </Form.Group> + + <Button type="submit"> + Luo Profiili + </Button> + </Form> + ) + } +} +export default ProfileForm; \ No newline at end of file diff --git a/wearright/src/components/bootstrap/Start.js b/wearright/src/components/bootstrap/Start.js new file mode 100644 index 0000000000000000000000000000000000000000..1a5b8898fd1083f5837f75bfea0a5a2a729078d9 --- /dev/null +++ b/wearright/src/components/bootstrap/Start.js @@ -0,0 +1,66 @@ +import React, { Component } from 'react'; +//import "../styles/Bootstrap/Start.css" +import Cookies from 'universal-cookie'; + +import ProfileForm from './ProfileForm'; +import StartWindow from './StartWindow'; + +class Start extends Component { + constructor(props) { + super(props); + + this.state = { + startWindow: false + }; + + this.handleProfileOnSubmit = this.handleProfileOnSubmit.bind(this); + this.handleCloseWindow = this.handleCloseWindow.bind(this); + } + + handleProfileOnSubmit(e) { + e.preventDefault(); + + const c = new Cookies(); + const d = new Date(); + + // Set expiration to 50 years ahead + d.setFullYear(d.getFullYear() + 50); + + const profile = { + name: e.target.username.value, + age: Number(e.target.age.value), + sex: e.target.sex.value, + city: e.target.city.value + } + + c.set('firstPage', 'true', { path: '/', expires: d }); + c.set('profile', [ profile ], { path: '/', expires: d }); + + window.location.reload(); + } + + handleCloseWindow() { + this.setState({ startWindow: true }); + } + + render() { + const {startWindow} = this.state; + + return ( + <div class="container"> + {startWindow + ? <div> + <h1>Luo ensimmäinen profiili</h1> + <ProfileForm + profileOnSubmit={this.handleProfileOnSubmit} + /> + </div> + : <StartWindow + closeWindow={this.handleCloseWindow} + />} + </div> + ) + + } +} +export default Start; \ No newline at end of file diff --git a/wearright/src/components/bootstrap/StartWindow.js b/wearright/src/components/bootstrap/StartWindow.js new file mode 100644 index 0000000000000000000000000000000000000000..af2664feb3d96de2da5bc89728da395826ff3d76 --- /dev/null +++ b/wearright/src/components/bootstrap/StartWindow.js @@ -0,0 +1,26 @@ +import React, { Component } from 'react'; + +import { Button } from 'react-bootstrap'; + +class StartWindow extends Component { + render() { + const {closeWindow} = this.props; + + return ( + <div> + <h1>Tervetuloa käyttämään Wear Right -palvelua!</h1> + <p> + Pukeutuminen ei ole koskaan ollut näin helppoa! Oli ulkona mikä sää tahansa, Wear Rightin avulla tiedät täsmälleen, miten pukeutua eri säätiloihin. + Saat säätilat suoraan laitteeseesi reaaliajassa, sekä sään mukaiset pukeutumisehdotukset. Voit lisätä palveluun eri profiileja, joten saat + pukeutumisehdotukset koko perheelle! Palveluun voi lisätä myös suosikkikaupunkisi, jolloin saat säätiedot haluamastasi paikasta. + </p> + <p> + Ennen, kun voit aloittaa, sinun täytyy luoda ensimmäinen profiili palveluun. Voit muokata profiileja myöhemmin milloin tahansa. Wear Right ei + tallenna henkilötietoja minnekään, vaan suosikkiprofiilisi näkyvät vain tässä laitteeessa. + </p> + <Button onClick={closeWindow}>Asia kunnossa!</Button> + </div> + ) + } +} +export default StartWindow; \ No newline at end of file diff --git a/wearright/src/components/bootstrap/WeatherContainer.js b/wearright/src/components/bootstrap/WeatherContainer.js new file mode 100644 index 0000000000000000000000000000000000000000..2206aeacf93168eb42a01ca6ec019611e47545c8 --- /dev/null +++ b/wearright/src/components/bootstrap/WeatherContainer.js @@ -0,0 +1,204 @@ +import React, { Component } from 'react'; + +// rc-slider +import Slider from "rc-slider"; +import "rc-slider/assets/index.css"; + +import { Row, Col } from 'react-bootstrap'; + +class WeatherContainer extends Component { + constructor(props) { + super(props); + + this.prepareData(props.profile); + + const d = new Date(); + + this.state = { + currentSelectedTime: 0, + currentSelectedDate: `${d.getDate()}.${d.getMonth() + 1}`, + weather: null, + forecast: null + } + + this.timeSliderOnAfterChange = this.timeSliderOnAfterChange.bind(this); + this.dateSliderOnAfterChange = this.dateSliderOnAfterChange.bind(this); + this.sliderAfter = this.sliderAfter.bind(this); + this.prepareData = this.prepareData.bind(this); + this.determineWeatherText = this.determineWeatherText.bind(this); + } + + prepareData(profile) { + let url = 'http://207.154.210.234/weather?q='; + const cors = 'http://cors-anywhere.herokuapp.com/'; + + fetch(cors + url + profile.city) + .then(res => res.json()) + .then(result => { + this.setState({ weather: result }); + this.props.applyShownForecast(result); + }); + + url = 'http://207.154.210.234/forecast?q=' + fetch(cors + url + profile.city) + .then(res => res.json()) + .then(result => { + this.setState({ forecast: result }); + }); + } + + handleDate(city) { + const tempObject = {}; + + const d = new Date(); + const curDate = d.getDate(); + + for (let i = 0; i < 5; i++) { + d.setDate(curDate + i); + + const text = `${d.getDate()}.${d.getMonth() + 1}`; + tempObject[i] = text; + } + + return tempObject; + } + + timeSliderOnAfterChange(step) { + const timeStep = step * 3; + this.setState({ currentSelectedTime: timeStep }); + + this.sliderAfter(timeStep, null); + } + + dateSliderOnAfterChange(step) { + const d = new Date(); + d.setDate(d.getDate() + step); + + const date = `${d.getDate()}.${d.getMonth() + 1}`; + this.setState({ currentSelectedDate: date }); + + this.sliderAfter(null, date); + } + + sliderAfter(timeStep, dateStep) { + const { weather, forecast, currentSelectedDate, currentSelectedTime } = this.state; + + const mostAccurateDate = (dateStep) ? dateStep : currentSelectedDate; + const mostAccurateTime = (timeStep !== null) ? timeStep : currentSelectedTime; + + const d = new Date(); + const dComp = `${d.getDate()}.${d.getMonth() + 1}`; + + let next = null; + + if (mostAccurateDate === dComp && mostAccurateTime === 0) { + this.props.applyShownForecast(weather); + return; + } + else if (mostAccurateDate === dComp && mostAccurateTime > 0) { + const nextHour = Math.round((d.getHours() + mostAccurateTime) / 3) * 3; + const nextDay = Math.floor(nextHour / 24); + const actualHours = Math.round((nextHour - (nextDay * 24)) / 3) * 3; + + d.setDate(d.getDate() + nextDay); + + next = forecast[`${d.getDate()}.${d.getMonth() + 1}`][actualHours]; + } else { + next = forecast[mostAccurateDate][mostAccurateTime]; + } + + this.props.applyShownForecast(next); + } + + determineWeatherText() { + const { currentSelectedDate, currentSelectedTime } = this.state; + + const d = new Date(); + const dComp = `${d.getDate()}.${d.getMonth() + 1}`; + + if (currentSelectedDate === dComp && currentSelectedTime === 0) { + return "Sää nyt"; + } + else if (currentSelectedDate === dComp && currentSelectedTime > 0) { + return `Sää noin ${currentSelectedTime} tunnin päästä` + } else { + return `Sää ${currentSelectedDate}, ${currentSelectedTime}:00`; + } + } + + render() { + const { forecast } = this.state; + const { shownForecast } = this.props; + + // Handle loading for current forecast + let curWeather = + <div className="weatherLabels"> + <p>Lämpötila: Lataa...</p> + <p>Tuuli: Lataa...</p> + <p>Tuntuu kuin: Lataa...</p> + </div> + + if (shownForecast) { + curWeather = + <div className="weatherLabels"> + <p>Lämpötila: {shownForecast.main.temp}</p> + <p>Tuuli: {shownForecast.wind.speed}m/s</p> + <p>Tuntuu kuin: {shownForecast.feel}</p> + </div> + } + + return ( + <Row> + <Col + lg={4} + md={6} + sm={12} + > + <h3><small>Säädä Säätiedotusta</small></h3> + <Slider + min={0} + max={4} + marks={this.handleDate()} + onAfterChange={this.dateSliderOnAfterChange} + disabled={(forecast) ? false : true} + /> + + <br /> + + <Slider + min={0} + max={7} + marks={{ + 0: "+0.00", + 1: "+3.00", + 2: "+6.00", + 3: "+9.00", + 4: "+12.00", + 5: "+15.00", + 6: "+18.00", + 7: "+21.00" + }} + onAfterChange={this.timeSliderOnAfterChange} + disabled={(forecast) ? false : true} + /> + </Col> + + <Col + lg={{ span: 6, offset: 2 }} + md={6} + sm={12} + > + <h3> + <small> + <span> + {this.determineWeatherText()} + </span> + </small> + </h3> + {curWeather} + </Col> + </Row> + ) + } +} +export default WeatherContainer; \ No newline at end of file diff --git a/wearright/src/components/bootstrap/home.js b/wearright/src/components/bootstrap/home.js new file mode 100644 index 0000000000000000000000000000000000000000..43f866c132dac0b5060e48cff55d93f77575dffd --- /dev/null +++ b/wearright/src/components/bootstrap/home.js @@ -0,0 +1,52 @@ +import React, { Component } from 'react'; + +import WeatherContainer from './WeatherContainer'; +import Clothes from './Clothes'; + +class Home extends Component { + constructor(props) { + super(props); + + this.state = { + clothes: null, + appliedForecast: null + } + + this.applyShownForecast = this.applyShownForecast.bind(this); + } + + applyShownForecast(forecast) { + const { currentSelectedProfile } = this.props; + + this.setState({ appliedForecast: forecast }); + + const url ='http://207.154.210.234/get/clothing?a='+ currentSelectedProfile.age + "&t=" + forecast.feel + const cors = 'http://cors-anywhere.herokuapp.com/' + + fetch(cors + url) + .then(res => res.json()) + .then(result => { + this.setState({ clothes: result }); + }); + } + + render() { + const { currentSelectedProfile } = this.props; + + return ( + <div> + <h1>Hyvää iltaa {currentSelectedProfile.name}</h1> + <WeatherContainer + profile = {currentSelectedProfile} + applyShownForecast = {this.applyShownForecast} + shownForecast = {this.state.appliedForecast} + /> + + <Clothes + clothing = {this.state.clothes} + /> + </div> + ) + } +} +export default Home; \ No newline at end of file diff --git a/wearright/src/styles/bootstrap/Start.css b/wearright/src/styles/bootstrap/Start.css new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391