<repo_name>rignellj/Full-Stack-Open-2021<file_sep>/Part_4/blogilista/utils/middleware.js
const { SECRET } = require('./config');
const jwt = require('jsonwebtoken');
const User = require('../models/user');

const errorHandler = (error, request, response, next) => {
	const { message, name } = error;

	if (name === 'ValidationError') {
		return response.status(400).send({ error: message });
	} else if (name === 'JsonWebTokenError') {
		return response.status(401).json({
			error: 'invalid token'
		});
	} else if (name === 'TokenExpiredError') {
		return response.status(401).json({
			error: 'token expired'
		});
	}
	next(error);
};

const tokenExtractor = (req, res, next) => {
	const getTokenFrom = req => {
		const auth = req.get('authorization');
		if (auth && auth.toLowerCase().startsWith('bearer ')) {
			return auth.substring(7);
		}
		return null;
	};
	req.token = getTokenFrom(req);
	next();
};

const userExtractor = async (req, res, next) => {
	const getUser = async (req) => {
		const { token } = req;
		const { id } = jwt.verify(token, SECRET);
		if (!token || !id) {
			return res.status(401).json({ error: 'token missing or invalid' });
		}
		return await User.findById(id);
	};
	req.user = await getUser(req);
	next();
};

module.exports = { errorHandler, tokenExtractor, userExtractor };
<file_sep>/Part_3/puhelinluettelo/models/contact.js
const mongoose = require('mongoose');

const url = process.env.MONGODB_URI;

mongoose.connect(url, {
	useNewUrlParser: true,
	useUnifiedTopology: true,
	useFindAndModify: false,
	useCreateIndex: true
})
	.then(() => {
		console.log('connected to MongoDB');
	})
	.catch(err => {
		console.log('error connecting to MongoDB: ', err.message);
	});

const contactSchema = new mongoose.Schema({
	name: {
		type: String,
		minlength: 3,
		required: true
	},
	number: {
		type: String,
		minlength: 10,
		required: true
	}
});

contactSchema.set('toJSON', {
	transform: (document, returnedObject) => {
		returnedObject.id = returnedObject._id.toString();
		delete returnedObject._id;
		delete returnedObject.__v;
	}
});

module.exports = mongoose.model('Contact', contactSchema);
<file_sep>/Part_2/puhelinluettelo_front/src/App.js
import React, { useState, useEffect } from 'react';

import service from './services/address';
import PersonForm from './components/PersonForm';
import Persons from './components/Persons';
import Filter from './components/Filter';
import Notification from './components/Notification';
import './App.css';

const App = () => {
  const [ persons, setPersons ] = useState(undefined);
  const [ newName, setNewName ] = useState('');
  const [ newNumber, setNewNumber ] = useState('');
  const [ filterName, setFilterName ] = useState('');
  const [ notification, setNotification ] = useState([null, null]);

  const onSubmitHandler = (event) => {
    event.preventDefault();
    const filteredPersons = persons.filter(person => person.name === newName.trim());
    const { length } = filteredPersons;

    if (length === 0) {
      const newAddress = {
        name: newName,
        number: newNumber
      };
      service
      .create(newAddress)
      .then(res => {
        setPersons(persons.concat(res.data));
        setNotification([`Added ${newAddress.name}`, 'added']);
      })
      .catch(err => {
        setNotification([`${err.response.data.error}`, null]);
      })
    } else {
      const confirm = window.confirm(`${newName.trim()} is already added to phonebook, replace the old with a new one?`);
      const person = filteredPersons[0];
      
      if (confirm) {
        const updatedAddress = {
          name: newName.trim(),
          number: newNumber
        };
        service
        .update(person.id, updatedAddress)
        .then(res => {
          console.log(res.data);
          setNotification([`Number was changed for ${updatedAddress.name}`, 'changed']);
        })
        .catch(err => {
          console.log(err);
          setNotification([`Address ${updatedAddress.name} was already deleted from server`, null]);
        });
      }
    }
    setTimeout(() => {
      setNotification([null, null])
      }, 5000)
    setNewName('');
    setNewNumber('');
  };

  const onNameChangeHandler = (event) => setNewName(event.target.value);
  
  const onNumberChangeHandler = (event) => setNewNumber(event.target.value);

  const filterCHangeHandler = (event) => setFilterName(event.target.value);

  useEffect(() => {
    service
    .getAll()
    .then(res => {
        console.log(res);
        setPersons(res.data);
    })
    .catch(err => {
      console.log(err);
    });
  }, []);

  return (
   <React.Fragment>
     <h2>Phonebook</h2>
     <Notification notification={notification} />
     <Filter
        filterCHangeHandler={filterCHangeHandler}
        filterName={filterName}
      />
     <h3>Add a new</h3>
      <PersonForm
        onSubmitHandler={onSubmitHandler}
        onNameChangeHandler={onNameChangeHandler}
        onNumberChangeHandler={onNumberChangeHandler}
        newName={newName}
        newNumber={newNumber}
      />
      <h2>Numbers</h2>
      <Persons
        setNotification={setNotification}
        filterName={filterName}
        persons={persons}
        setPersons={setPersons}
      />
   </React.Fragment>
  );
}

export default App;
<file_sep>/Part_2/Kurssitiedot/src/components/Course.js
import React from 'react';

const Header = ({ name }) => <h1>{name}</h1>;

const Part = ({ name, exercises }) => <p>{name} {exercises}</p>;

const Total = ({ total }) => <p>Number of exercises {total}</p>;

const Content = ({ parts }) => {
	return (
		<React.Fragment>
			{parts.map(part =>
				<Part
					key={part.id}
					name={part.name}
					exercises={part.exercises}
				/>
			)}
		</React.Fragment>
	);
};

const Course = ({ course }) => {
	const reducer = (accumulator, currentValue) => {
		return {
			exercises: accumulator.exercises + currentValue.exercises
		};
	};
	const { name, parts } = course;
	const { exercises } = parts.reduce(reducer);

	return (
		<React.Fragment>
			<Header name={name} />
			<Content parts={parts} />
			<Total total={exercises}/>
		</React.Fragment>
	);
};

export default Course;
<file_sep>/Part_2/maiden_tiedot/src/App.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';

import Countries from './components/Countries';
import './App.css';

const App = () => {
  const [ countries, setCountries ] = useState([]);
  const [ countryName, setCountryName ] = useState([]);
  const countriesURL = 'https://restcountries.eu/rest/v2/all';
  
  const inputChangeHandler = (event) => setCountryName(event.target.value);

  useEffect(() => {
    axios.get(countriesURL)
    .then(res => {
      setCountries(res.data)
    });
  }, []);

  return (
    <React.Fragment>
      <h1>Find Countries</h1>
      <input value={countryName} onChange={inputChangeHandler} />
      <Countries countries={countries} countryName={countryName} setCountryName={setCountryName} />
    </React.Fragment>
  );
}

export default App;
<file_sep>/Part_4/blogilista/tests/blog_api.test.js
const mongoose = require('mongoose');
const supertest = require('supertest');
const app = require('../app');
const api = supertest(app);
const Blog = require('../models/blog');
const {
	initialBlogs,
	newBlog,
	newBlogWithoutLikes,
	blogWithoutUrl,
	blogWithoutTitle,
	blogWithoutTitleOrUrl,
	blogToBeDeleted,
	apiUrl
} = require('./blog_helper.test');

beforeEach(async () => {
	await Blog.deleteMany({});

	const blogObjects = initialBlogs.map(blog => new Blog(blog));
	const promiseArray = blogObjects.map(blog => blog.save());
	await Promise.all(promiseArray);
});

describe('form', () => {
	test('blogs are returned as JSON', async () => {
		await api
			.get(apiUrl)
			.expect(200)
			.expect('Content-Type', /application\/json/);
	});
});

describe('blogs based tests', () => {
	test('all blogs are returned', async () => {
		const { body } = await api.get(apiUrl);

		expect(body).toHaveLength(initialBlogs.length);
	});
	test('a spesific url is within returned blogs', async () => {
		const { body } = await api.get(apiUrl);
		const contents = body.map(item => item.url);

		expect(contents).toContain('www.com.www');
	});
	test('id property check', async () => {
		const { body } = await api.get(apiUrl);
		const ids = body.map(item => item.id);

		expect(ids[0]).toBeDefined();
	});
});

describe('routes', () => {
	test('new blog can be added', async () => {
		await api
			.post(apiUrl)
			.send(newBlog)
			.expect(201)
			.expect('Content-Type', /application\/json/);

		const { body } = await api.get(apiUrl);
		const titles = body.map(item => item.title);
		expect(body).toHaveLength(initialBlogs.length + 1);
		expect(titles).toContain('new blog can be added');
	});
	test('likes are initialized to 0 if not specified', async () => {
		const { body } = await api
			.post(apiUrl)
			.send(newBlogWithoutLikes)
			.expect(201);
		expect(body.likes).toBe(0);
	});
	test('post request without url or title fails with 400 status code', async () => {
		await api
			.post(apiUrl)
			.send(blogWithoutUrl)
			.expect(400);
		await api
			.post(apiUrl)
			.send(blogWithoutTitle)
			.expect(400);
		await api
			.post(apiUrl)
			.send(blogWithoutTitleOrUrl)
			.expect(400);
	});
	test('delete request returns 204 status code', async () => {
		const { body } = await api
			.post(apiUrl)
			.send(blogToBeDeleted)
			.expect(201);
		const { id } = body;
		await api
			.delete(apiUrl + '/' + id)
			.expect(204);
	});
	test('likes increase by one if blog is updated', async () => {
		const { body } = await api
			.post(apiUrl)
			.send(newBlogWithoutLikes)
			.expect(201);
		const { id } = body;
		const response = await api
			.put(apiUrl + '/' + id)
			.send(body);
		expect(response.body.likes).toBe(1);
	});
});

afterAll(() => {
	mongoose.connection.close();
});
<file_sep>/Part_4/blogilista/tests/blog_helper.test.js
const initialBlogs = [
	{
		title: '1',
		author: '1',
		url: 'www.com'
	},
	{
		title: '2',
		author: '2',
		url: 'www.com'
	},
	{
		title: '3',
		author: '3',
		url: 'www.com.www'
	},
];
const newBlog = {
	title: 'new blog can be added',
	author: '4',
	url: 'www.fi',
	likes: 7
};
const newBlogWithoutLikes = {
	title: 'blog without likes',
	author: '5',
	url: 'www.fi.www'
};

const blogWithoutUrl = {
	title: 'blog without likes',
	author: '6',
	likes: 1
};

const blogWithoutTitle = {
	author: '7',
	url: 'www.fi.www',
	likes: 1
};

const blogWithoutTitleOrUrl = {
	author: '8',
	likes: 1
};

const blogToBeDeleted = {
	title: 'blog to be deleted',
	author: '5',
	url: 'www.fi.www',
	likes: 2
};

const apiUrl = '/api/blogs';

module.exports = {
	initialBlogs,
	newBlog,
	newBlogWithoutLikes,
	blogWithoutUrl,
	blogWithoutTitle,
	blogWithoutTitleOrUrl,
	blogToBeDeleted,
	apiUrl
};
<file_sep>/Part_3/puhelinluettelo/README.md
My Heroku deploy

https://phonebook-fso2021.herokuapp.com/
<file_sep>/Part_4/blogilista/utils/list_helper.js
const dummy = () => {
	return 1;
};

const totalLikes = (arrayOfBlogs) => {
	const reducer = (sum, item) => {
		return sum + item.likes;
	};
	if (arrayOfBlogs.length === 0) {
		return 0;
	}
	return arrayOfBlogs.reduce(reducer, 0);
};

const favoriteBlog = (arrayOfBlogs) => {
	const reducer = (favorite, item) => {
		return favorite.likes > item.likes ? favorite : item;
	};
	return arrayOfBlogs.reduce(reducer);
};

module.exports = { dummy, totalLikes, favoriteBlog };
<file_sep>/Part_4/blogilista/tests/user_api.test.js
const mongoose = require('mongoose');
const supertest = require('supertest');
const app = require('../app');
const api = supertest(app);
const User = require('../models/user');
const {
	initialUsers,
	apiURL,
	invalidUser,
	invalidUser2
} = require('./user_helper.test');

beforeEach(async () => {
	await User.deleteMany({});

	const userObjects = initialUsers.map(user => new User(user));
	const promiseArray = userObjects.map(user => user.save());
	await Promise.all(promiseArray);
});

describe('user creation', () => {
	test('creating a user that alreaydy exists', async () => {
		await api
			.post(apiURL)
			.send(initialUsers[0])
			.expect(400);
	});
	test('creating two invalid users', async () => {
		await api
			.post(apiURL)
			.send(invalidUser)
			.expect(400);
		await api
			.post(apiURL)
			.send(invalidUser2)
			.expect(400);
	});
});

afterAll(() => {
	mongoose.connection.close();
});
<file_sep>/Part_4/blogilista/tests/user_helper.test.js
const initialUsers = [
	{
		username: 'jrignell',
		password: '<PASSWORD>',
		name: '<NAME>'
	},
	{
		username: 'hellas',
		password: '<PASSWORD>',
		name: '<NAME>'
	},
	{
		username: 'mluukkai',
		password: '<PASSWORD>',
		name: '<NAME>'
	}
];

const invalidUser = {
	name: 'Martta'
}

const invalidUser2 = {
	name: 'Martta',
	username: 'marttaOnHirmuinenKartta',
	password: '0'
}

const apiURL = '/api/users';

module.exports = { initialUsers, apiURL, invalidUser, invalidUser2 };<file_sep>/Part_3/puhelinluettelo/index.js
require('dotenv').config();
const express = require('express');
const morgan = require('morgan');
const cors = require('cors');
const app = express();
const Contact = require('./models/contact');

app.use(express.json());
app.use(express.static('build'));
app.use(cors());

const errorHandler = (err, req, res, next) => {
	const { message, name } = err;

	console.log(message);
	if (name === 'CastError') {
		return res.status(400).send({ error: 'malformatted id' });
	} else if (name === 'ValidationError') {
		return res.status(400).send({ error: message });
	}
	next(err);
};

morgan.token('post_content', function getContent (req) {
	return JSON.stringify(req.body);
});

app.use(morgan(':method :url :status :res[content-length]  - :response-time ms :post_content', {
	skip: (req) => {
		return req.method !== 'POST';
	}
}));

app.get('/info', (req, res, next) => {
	Contact.find({})
		.then(result => {
			const content = `<p>Phone book has info for ${result.length} people</p><p>${new Date()}</p>`;
			res.send(content);
		})
		.catch(err => next(err));
});

app.get('/api/persons', (req, res, next) => {
	Contact.find({})
		.then(contact => res.json(contact))
		.catch(err => next(err));
});

app.get('/api/persons/:id', (req, res, next) => {
	Contact.findById(req.params.id)
		.then(contact => {
			if (contact) {
				res.json(contact);
			} else {
				res.status(404).end();
			}
		})
		.catch(err => next(err));
});

app.post('/api/persons', (req, res, next) => {
	const { body } = req;
	const { name, number } = body;

	Contact.find({})
		.then(result => {
			const foundContact = result.filter(person => person.name === name);
			if (foundContact.length > 0) {
				return res.status(400).json({ error: 'Person is already in the PhoneBook' });
			}
		})
		.catch(err => next(err));
	const person = new Contact({ name, number });

	person.save()
		.then(savedPerson => res.json(savedPerson))
		.catch(err => next(err));
});

app.delete('/api/persons/:id', (req, res, next) => {
	Contact.findByIdAndRemove(req.params.id)
		.then(() => res.status(204).end())
		.catch(err => next(err));
});

app.put('/api/persons/:id', (req, res, next) => {
	const { body, params } = req;
	const { name, number } = body;

	const contact = { name, number };
	Contact.findByIdAndUpdate(params.id, contact, { new: true })
		.then(updatedContact => res.json(updatedContact))
		.catch(err => next(err));
});

const unknowEndPoint = (req, res) => {
	res.status(404).send({ error: 'Unknown error' });
};

app.use(unknowEndPoint);

app.use(errorHandler);

const PORT = process.env.PORT;
app.listen(PORT || 3001, () => {
	console.log(`Server running on port ${PORT}`);
});
<file_sep>/Part_1/unicafe/src/App.js
import React, { useState } from 'react';

import './App.css';

const Header = () => {
  return <h1>Give feedback</h1>
};

const Button = ({ option, clickHandler }) => {
  const handleClick = (event) => {
    clickHandler(prevValue => prevValue + 1);
  };
  return (
    <button
      value={option}
      type="submit"
      onClick={handleClick}
    >
      {option}
    </button>
  );
};

const StatisticsLine = ({ text, value, percent }) => {
  return (
    <tr>
      <td>{text}</td>
      <td>{value} {percent ? '%' : ''}</td> 
    </tr>
  );
};

const Statistics = ({ goods, neutrals, bads }) => {
  const all = goods + neutrals + bads;
  const positive = all === 0 ? 0 : goods / all * 100;
  let content;

  if (all === 0) {
    content = <p>No feedback given</p>
  } else {
    content = (
      <React.Fragment>
        <h1>Statistics</h1>
        <table>
          <tbody>
            <StatisticsLine text="good" value={goods} />
            <StatisticsLine text="neutral" value={neutrals} />
            <StatisticsLine text="bad" value={bads} />
            <StatisticsLine text="all" value={all} />
            <StatisticsLine text="positive" percent={true} value={positive} />
          </tbody>
        </table>
      </React.Fragment>
    )
  }
  return (
    <React.Fragment>
      {content}
    </React.Fragment>
  );
};

const  App = () => {
  const [good, setGood] = useState(0);
  const [neutral, setNeutral] = useState(0);
  const [bad, setBad] = useState(0);

  return (
    <React.Fragment>
      <Header />
      <Button
        option="Good"
        clickHandler={setGood}
        value={good}
      />
      <Button
        option="Neutral"
        clickHandler={setNeutral}
        value={neutral}
      />
      <Button
        option="Bad"
        clickHandler={setBad}
        value={bad}
      />
      <Statistics goods={good} neutrals={neutral} bads={bad} />
    </React.Fragment>
  );
}

export default App;
<file_sep>/Part_2/puhelinluettelo_front/src/components/Person.js
import React from 'react';

import service from '../services/address';

const Person = ({ name, number, id, setPersons, setNotification }) => {
	const deleteHandler = (id) => {
		const confirm = window.confirm(`Delete ${name}?`);
		if (confirm) {
			service
			.deleteAddress(id)
			.then(res => {
				console.log(res, 'delete address');
			})
			setNotification([`Deleted ${name}`, 'deleted'])
			setTimeout(() => {
				setNotification([null, null])
			  }, 5000)
			service
			.getAll()
			.then(res => {
				setPersons(res.data);
			});
		}
	};
	return (
	  <React.Fragment>
		<tr>
		  <td>{name}</td>
		  <td>{number}</td>
		  <td><button onClick={() => deleteHandler(id)}>Delete</button></td>
		</tr>
	  </React.Fragment>
	);
};

export default Person;
<file_sep>/Part_4/blogilista/controllers/blogs.js
const blogsRouter = require('express').Router();
const Blog = require('../models/blog');

blogsRouter.get('/', async (req, res) => {
	const blogs = await Blog
		.find({})
		.populate('userId', { username: 1, name: 1, id: 1 });
	res.json(blogs);
});

blogsRouter.post('/', async (req, res, next) => {
	const { body, user } = req;
	let { title, author, url, likes } = body;

	likes = !likes ? 0 : likes;
	if (!title || !url) {
		return res.status(400).end();
	}
	try {
		const blog = new Blog({ title, author, url, likes, user: user._id });
		const result = await blog.save();

		user.blogs = user.blogs.concat(result._id);
		await user.save();

		res.status(201).json(result);

	} catch (error) {
		next(error);
	}
});

blogsRouter.delete('/:id', async (req, res) => {
	const { params, user } = req;
	const { userId } = await Blog.findById(params.id).populate('userId');
	const { _id: blogId } = userId;

	if (user._id.toString() === blogId.toString()) {
		await Blog.findOneAndDelete(params.id);
	}
	res.status(204).end();
});

blogsRouter.put('/:id', async (req, res) => {
	const { body, params } = req;
	const { likes, title, author, url } = body;

	const blog = {
		likes: likes + 1,
		title,
		author,
		url
	};
	const updatedBlog = await Blog.findByIdAndUpdate(params.id, blog, { new: true });
	res.json(updatedBlog);
});

module.exports = blogsRouter;
<file_sep>/Part_2/puhelinluettelo_front/src/components/Filter.js
import React from 'react';

const Filter = ({ filterName, filterCHangeHandler }) => {
	return (
		<div>
      	  filter: <input onChange={filterCHangeHandler} value={filterName} />
      	</div>
	);
};

export default Filter;
<file_sep>/Part_2/puhelinluettelo_front/src/components/PersonForm.js
import React from 'react';

const PersonForm = ({
		onSubmitHandler,
		onNameChangeHandler,
		newName,
		onNumberChangeHandler,
		newNumber
	}) => {
	return (
		<form onSubmit={onSubmitHandler}>
			<div>
				name: <input onChange={onNameChangeHandler} value={newName} />
			</div>
			<div>
				number: <input onChange={onNumberChangeHandler} value={newNumber} />
			</div>
			<div>
				<button type="submit">Add</button>
			</div>
      </form>
	);
};

export default PersonForm;
<file_sep>/Part_2/puhelinluettelo_front/src/components/Notification.js
import React from 'react';

import styles from './Notification.module.css';

const Notification = ({ notification }) => {
	const [ message, event ] = notification;
	if (message === null) {
		return null
	}
	if (event) {
		return (
			<div className={`${styles.notification} ${styles.ok}`}>
				{message}
			</div>
		);
	}
	return (
		<div className={`${styles.notification} ${styles.error}`}>
			{message}
		</div>
	);
};

export default Notification;
<file_sep>/Part_3/tietokanta_komentorivilta/mongo.js
const mongoose = require('mongoose');

let name = null;
let number = null;

if (process.argv.length == 5) {
	name = process.argv[3];
	number = process.argv[4];
} else if (process.argv.length == 3) {
} else {
	console.log('Give password, name and number as argument');
	console.log('OR');
	console.log('Give just password to get all records from database');
	console.log('Exiting with 1');
	process.exit(1);
}
const password = process.argv[2];
const url = `mongodb+srv://tunnus:${password}@<EMAIL>.<EMAIL>/phonebook?retryWrites=true&w=majority`

mongoose.connect(url, {
	useNewUrlParser: true,
	useUnifiedTopology: true,
	useFindAndModify: false,
	useCreateIndex: true
});

const contactSchema = new mongoose.Schema({
	name: String,
	number: String
});

const Contact = mongoose.model('Contact', contactSchema);


if (name && number) {
	const contact = new Contact({
		name: name,
		number: number
	});
	contact.save()
	.then(res => {
		console.log(`Contact name: ${name}, number: ${number} saved to phonebook!`);
		console.log(res);
		mongoose.connection.close();
	});
} else {
	Contact.find({})
	.then(res => {
		console.log('Phonebook:');
		res.forEach(contact => console.log(`${contact.name} ${contact.number}`));
		mongoose.connection.close();
	});
}
<file_sep>/Part_2/puhelinluettelo_front/src/components/Persons.js
import React from 'react';

import Person from './Person';

const Persons = ({ persons, filterName, setPersons, setNotification  }) => {
	let content;
	if (persons === undefined) {
		return <p>Loading...</p>;
	}
	const filteredPersons = persons.filter(person => person.name.includes(filterName));
	if (filteredPersons.length || filterName) {
		content = (
			filteredPersons.map(person => (
				<Person
					setNotification={setNotification}
					setPersons={setPersons}
					id={person.id}
					key={person.name}
					name={person.name}
					number={person.number}
				/>
			)
		));
	} else {
		content = (
			persons.map(person => (
				<Person
					setNotification={setNotification}
					setPersons={setPersons}
					id={person.id}
					key={person.name}
					name={person.name}
					number={person.number}
				/>
			)
		));
	}

	return (
		<table>
        	<tbody>
          		{content}
       	 	</tbody>
      </table>
	);
};

export default Persons;
<file_sep>/Part_2/maiden_tiedot/src/components/Country.js
import React, { useEffect, useState } from 'react';
import axios from 'axios';

import Weather from './Weather';

const Country = ({ country }) => {
	const { name, capital, languages, population, flag } = country;
	const api_key = process.env.REACT_APP_API_KEY;
	const [ weather, setWeather ] = useState(undefined);
	const weatherURL = `http://api.weatherstack.com/current?access_key=${api_key}&query=${capital}`

	useEffect(() => {
	  axios.get(weatherURL)
	  .then(res => {
		setWeather(res.data);
	  });
	}, [weatherURL]);
  
	return (
	  <React.Fragment>
		<h2>{name}</h2>
		<p>Capital: {capital}</p>
		<p>Population: {population}</p>
		<h3>Languages</h3>
		<ul>
		  {languages.map(language => <li key={language.name}>{language.name}</li>)}
		</ul>
		<img src={flag} style={{width:"5rem", height:"auto"}} alt="flag" />
		<Weather capital={capital} weather={weather} />
	  </React.Fragment>
	);
};

export default Country;
<file_sep>/Part_2/maiden_tiedot/src/components/Weather.js
import React from 'react';

const Weather = ({ weather, capital }) => {
	let content;
	
	if (weather === undefined) {
	  content = <p></p>
	} else {
	  const { temperature, weather_icons } = weather.current
	  content = (
		<React.Fragment>
		  <p>Temperature: {temperature} °C</p>
		  <img src={weather_icons} style={{width:"5rem", height:"auto"}} alt="weather" />
		</React.Fragment>
	  )
	}

	return (
	  <React.Fragment>
		<h3>Weather in {capital}</h3>
		{content}
	  </React.Fragment>
	);
};

export default Weather;
