first commit

This commit is contained in:
2022-01-21 11:34:23 +01:00
commit bf2ef01f18
26 changed files with 17077 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
.DS_Store
node_modules
coverage
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+28
View File
@@ -0,0 +1,28 @@
# worldcountries
A small responsive app build with Vue, Vuex, Axios and Bulma. It calls the restcountries API, visualizes the list of countries, and shows detailed information for each country in a modal.
## Project setup
```
clone the project to your local drive with
git clone https://github.com/dadobos/worldcountries.git
go inside the project with
cd worldcountries
install the packages with
npm install
start the project with
npm run serve
it is now available in your browser on http://localhost:8080/
start testing with
npm test
check full coverage with
npm run coverage
```
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]],
};
Executable
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env sh
# abort on errors
set -e
# build
npm run build
# navigate into the build output directory
cd dist
# if you are deploying to a custom domain
# echo 'www.example.com' > CNAME
git add -A
git commit -m 'deploy'
# if you are deploying to https://<USERNAME>.github.io
# git push -f git@github.com:<USERNAME>/<USERNAME>.github.io.git main
# if you are deploying to https://<USERNAME>.github.io/<REPO>
git push -f git@github.com:dadobos/worldcountries.git main:gh-pages
# git push -f git@github.com:<USERNAME>/world.git main:gh-pages
cd -
+1
View File
@@ -0,0 +1 @@
.modal{display:flex;flex-direction:column;box-shadow:2px 2px 20px 1px;margin:auto;width:-webkit-fit-content;width:-moz-fit-content;width:fit-content;max-width:90vw;height:-webkit-fit-content;height:-moz-fit-content;height:fit-content;max-height:80vh;overflow:hidden}.modal-background{position:fixed;top:0;right:0;bottom:0;left:0;background:#2c3e50;opacity:.6;cursor:pointer}.modal-card{overflow-y:auto;overflow-x:hidden;background-color:bisque}.pop-enter-active,.pop-leave-active{transition:transform .4s cubic-bezier(.5,0,.5,1),opacity .4s linear}.pop-enter,.pop-leave-to{opacity:0;transform:scale(.3) translateY(-50%)}.card{background-color:bisque;cursor:pointer}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;padding-top:60px}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<!DOCTYPE html><html lang="" style="background-color: #ddd;"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="https://raw.githubusercontent.com/diaid83/details/master/favicon.ico" type="image/x-icon"><title>worldcountries</title><link href="/worldcountries/css/app.69770c40.css" rel="preload" as="style"><link href="/worldcountries/css/chunk-vendors.53968962.css" rel="preload" as="style"><link href="/worldcountries/js/app.4b638670.js" rel="preload" as="script"><link href="/worldcountries/js/chunk-vendors.e88d19a5.js" rel="preload" as="script"><link href="/worldcountries/css/chunk-vendors.53968962.css" rel="stylesheet"><link href="/worldcountries/css/app.69770c40.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but worldcountries doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/worldcountries/js/chunk-vendors.e88d19a5.js"></script><script src="/worldcountries/js/app.4b638670.js"></script></body></html>
+2
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+16236
View File
File diff suppressed because it is too large Load Diff
+89
View File
@@ -0,0 +1,89 @@
{
"name": "worldcountries",
"version": "0.1.0",
"private": true,
"jest": {
"moduleFileExtensions": [
"js",
"json",
"vue"
],
"transform": {
".*\\.(vue)$": "vue-jest",
"^.+\\.js$": "babel-jest"
},
"snapshotSerializers": [
"<rootDir>/node_modules/jest-serializer-vue"
],
"watchPathIgnorePatterns": [
"/node_modules/"
],
"collectCoverage": true,
"coverageDirectory": "./coverage/",
"collectCoverageFrom": [
"**/*.{js,vue}",
"!**/node_modules/**",
"!**/coverage/**",
"!**/babel.config.js"
]
},
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test": "jest --watch",
"coverage": "jest --coverage",
"deploy": "sh deploy.sh"
},
"dependencies": {
"axios": "^0.21.1",
"bulma": "^0.9.1",
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vuex": "^3.6.0",
"vue-template-compiler": "^2.6.12"
},
"devDependencies": {
"@babel/core": "^7.12.10",
"@babel/preset-env": "^7.12.11",
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-service": "~4.5.0",
"@vue/test-utils": "^1.1.2",
"babel-core": "^7.0.0-bridge.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.6.3",
"eslint": "^6.7.2",
"eslint-plugin-jest": "^24.1.3",
"eslint-plugin-vue": "^6.2.2",
"jest": "^26.6",
"jest-serializer-vue": "^2.0.2",
"regenerator-runtime": "^0.13.7",
"vue-jest": "^3.0.7",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"plugins": [
"jest"
],
"env": {
"node": true,
"jest": true,
"browser": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
+21
View File
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="" style="background-color: #ddd;">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="https://raw.githubusercontent.com/diaid83/details/master/favicon.ico" type="image/x-icon">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+26
View File
@@ -0,0 +1,26 @@
<template>
<div id="app">
<Countries />
</div>
</template>
<script>
import "bulma/css/bulma.css";
import Countries from "./view/Countries.vue";
export default {
name: "App",
components: {
Countries,
},
};
</script>
<style>
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding-top: 60px;
}
</style>
+340
View File
@@ -0,0 +1,340 @@
<template>
<transition name="pop">
<div class="modal">
<div class="modal-background" @click="close"></div>
<div class="modal-card">
<div class="card-image" v-show="countryData.flag">
<figure class="image is-3by2 m-5">
<img :src="countryData.flag" alt="flag" />
</figure>
</div>
<div class="card-content has-text-justified">
<div class="columns is-mobile" v-show="countryData.name">
<h4 class="column is-4 is-half-mobile">Country:</h4>
<div class="column has-text-right-mobile">
{{ countryData.name }}
</div>
</div>
<div class="columns is-mobile" v-show="countryData.nativeName">
<h4 class="column is-4 is-half-mobile">Native Name:</h4>
<div class="column has-text-right-mobile">
{{ countryData.nativeName }}
</div>
</div>
<div class="columns is-mobile" v-show="countryData.demonym">
<h4 class="column is-4 is-half-mobile">Demonym:</h4>
<div class="column has-text-right-mobile">
{{ countryData.demonym }}
</div>
</div>
<div class="columns is-mobile" v-show="countryData.capital">
<div class="column is-4">Capital:</div>
<div class="column has-text-right-mobile">
{{ countryData.capital }}
</div>
</div>
<div class="columns is-mobile" v-show="countryData.region">
<div class="column is-4">Region:</div>
<div class="column has-text-right-mobile">
{{ countryData.region }}
</div>
</div>
<div class="columns is-mobile" v-show="countryData.subregion">
<div class="column is-4">Subregion:</div>
<div class="column has-text-right-mobile">
{{ countryData.subregion }}
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="countryData.translations"
>
<div class="column is-4">Translations:</div>
<div class="column is-8 has-text-right-mobile">
<div
v-for="(item, key, i) in countryData.translations"
:key="`${item}-${key}-${i}`"
>
{{ key }} - {{ item }}
</div>
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="countryData.currencies"
>
<div class="column is-4">Currencies:</div>
<div class="column is-8 has-text-right-mobile">
<div
v-for="(currency, i) in countryData.currencies"
:key="`item-${currency}-${i}`"
>
<div
v-for="(item, key, j) in currency"
:key="`${item}-${key}-${j}`"
>
{{ key }} - {{ item }}
</div>
</div>
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="countryData.languages"
>
<div class="column is-4">Languages:</div>
<div class="column is-8 has-text-right-mobile">
<div
v-for="(language, i) in countryData.languages"
:key="`item-${language}-${i}`"
>
<div
v-for="(item, key, j) in language"
:key="`${item}-${key}-${j}`"
>
{{ key }} - {{ item }}
</div>
</div>
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="countryData.regionalBlocs"
>
<div class="column is-4">Regional Blocs:</div>
<div class="column is-8 has-text-right-mobile">
<div
v-for="(regionalBloc, i) in countryData.regionalBlocs"
:key="`item-${regionalBloc}-${i}`"
>
<div
class="columns is-mobile"
v-for="(item, key, j) in regionalBloc"
:key="`${item}-${key}-${j}`"
>
<div class="column is-half">{{ key }}</div>
<div class="column">
<div v-if="typeof item === 'string'">
{{ item }}
</div>
<div v-else>
<div v-for="(text, k) in item" :key="`${text}-${k}`">
{{ text }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="countryData.altSpellings"
>
<div class="column is-4">Alt Spellings:</div>
<div class="column is-8 has-text-right-mobile">
<div
v-for="(item, i) in countryData.altSpellings"
:key="`item-${i}`"
>
{{ item }}
</div>
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="countryData.topLevelDomain"
>
<div class="column is-4">Top level domain:</div>
<div class="column is-8 has-text-right-mobile">
<div
v-for="(item, i) in countryData.topLevelDomain"
:key="`item-${i}`"
>
{{ item }}
</div>
</div>
</div>
<div class="columns is-mobile" v-show="countryData.alpha2Code">
<h4 class="column is-4 is-half-mobile">Alpha 2 code:</h4>
<div class="column has-text-right-mobile">
{{ countryData.alpha2Code }}
</div>
</div>
<div class="columns is-mobile" v-show="countryData.alpha3Code">
<h4 class="column is-4 is-half-mobile">Alpha 3 code:</h4>
<div class="column has-text-right-mobile">
{{ countryData.alpha3Code }}
</div>
</div>
<div class="columns is-mobile" v-show="countryData.numericCode">
<h4 class="column is-4 is-half-mobile">Numeric code:</h4>
<div class="column has-text-right-mobile">
{{ countryData.numericCode }}
</div>
</div>
<div class="columns is-mobile" v-show="countryData.area">
<h4 class="column is-4 is-half-mobile">Area:</h4>
<div class="column has-text-right-mobile">
{{ countryData.area }}
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="countryData.callingCodes"
>
<div class="column is-4">Calling codes:</div>
<div class="column is-8 has-text-right-mobile">
<div
v-for="(item, i) in countryData.callingCodes"
:key="`item-${i}`"
>
{{ item }}
</div>
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="countryData.latlng"
>
<div class="column is-4">Latitude longitude:</div>
<div class="column is-8 has-text-right-mobile">
<div v-for="(item, i) in countryData.latlng" :key="`item-${i}`">
{{ item }}
</div>
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="countryData.timezones"
>
<div class="column is-4">Timezones:</div>
<div class="column is-8 has-text-right-mobile">
<div
v-for="(item, i) in countryData.timezones"
:key="`item-${i}`"
>
{{ item }}
</div>
</div>
</div>
<div class="columns is-mobile" v-show="countryData.population">
<div class="column is-4">Population:</div>
<div class="column has-text-right-mobile">
<div v-if="countryData.population > 1000">
{{ Math.round(countryData.population / 1000) }}K
</div>
<div v-else>{{ countryData.population }}</div>
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="countryData.borders"
>
<div class="column is-4">Borders:</div>
<div class="column is-8 has-text-right-mobile">
<div v-for="(item, i) in countryData.borders" :key="`item-${i}`">
{{ item }}
</div>
</div>
</div>
<div class="columns is-mobile" v-show="countryData.cioc">
<h4 class="column is-4 is-half-mobile">Cioc:</h4>
<div class="column has-text-right-mobile">
{{ countryData.cioc }}
</div>
</div>
<div class="columns is-mobile" v-show="countryData.gini">
<h4 class="column is-4 is-half-mobile">Gini:</h4>
<div class="column has-text-right-mobile">
{{ countryData.gini }}
</div>
</div>
</div>
</div>
<button
class="modal-close is-large"
@click="close"
aria-label="close"
></button>
</div>
</transition>
</template>
<script>
export default {
name: "CountryModal",
props: ["countryData"],
methods: {
close() {
this.$emit("close");
},
},
};
</script>
<style>
.modal {
display: flex;
flex-direction: column;
box-shadow: 2px 2px 20px 1px;
margin: auto;
width: fit-content;
max-width: 90vw;
height: fit-content;
max-height: 80vh;
overflow: hidden;
}
.modal-background {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: #2c3e50;
opacity: 0.6;
cursor: pointer;
}
.modal-card {
overflow-y: auto;
overflow-x: hidden;
background-color: bisque;
}
/* ---------------------------------- */
.pop-enter-active,
.pop-leave-active {
transition: transform 0.4s cubic-bezier(0.5, 0, 0.5, 1), opacity 0.4s linear;
}
.pop-enter,
.pop-leave-to {
opacity: 0;
transform: scale(0.3) translateY(-50%);
}
</style>
+10
View File
@@ -0,0 +1,10 @@
import Vue from 'vue';
import App from './App.vue';
import store from './store';
Vue.config.productionTip = false;
new Vue({
store,
render: (h) => h(App),
}).$mount('#app');
+16
View File
@@ -0,0 +1,16 @@
import axios from 'axios';
const actions = {
async getCountries({ commit }) {
const url = 'https://restcountries.com/v2/all';
await axios
.get(url)
.then((response) => {
commit('SET_COUNTRIES', response.data);
})
.catch((error) => {
console.log(error);
});
},
};
export default actions;
+4
View File
@@ -0,0 +1,4 @@
const getters = {
allCountries: (state) => state.countries,
};
export default getters;
+15
View File
@@ -0,0 +1,15 @@
import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import getters from './getters';
import actions from './actions';
import mutations from './mutations';
Vue.use(Vuex);
export default new Vuex.Store({
state,
getters,
actions,
mutations,
});
+6
View File
@@ -0,0 +1,6 @@
const mutations = {
SET_COUNTRIES(state, countries) {
state.countries = countries;
},
};
export default mutations;
+4
View File
@@ -0,0 +1,4 @@
const state = {
countries: [],
};
export default state;
+126
View File
@@ -0,0 +1,126 @@
<template>
<div class="container is-fluid">
<div class="container is-max-fullhd">
<div class="columns is-multiline">
<div
class="column is-full-mobile is-half-tablet is-one-third-desktop is-one-quarter-fullhd"
v-for="(country, index) in countries"
:key="index"
>
<div class="card" @click="showModal(country)">
<div class="card-image" v-show="country.flag">
<figure class="image is-3by2 m-5">
<img :src="country.flag" alt="flag" />
</figure>
</div>
<div class="card-content has-text-justified">
<div class="columns is-mobile" v-show="country.name">
<h4 class="column is-4 is-half-mobile">Country:</h4>
<div class="column has-text-right-mobile">
{{ country.name }}
</div>
</div>
<div class="columns is-mobile" v-show="country.capital">
<div class="column is-4">Capital:</div>
<div class="column has-text-right-mobile">
{{ country.capital }}
</div>
</div>
<div class="columns is-mobile" v-show="country.region">
<div class="column is-4">Region:</div>
<div class="column has-text-right-mobile">
{{ country.region }}
</div>
</div>
<div
class="columns is-multiline is-mobile"
v-show="country.timezones"
>
<div class="column is-4">
<p>Timezones:</p>
</div>
<div class="column is-8 has-text-right-mobile">
<div
v-for="(item, i) in country.timezones"
:key="`item-${i}`"
>
{{ item }}
</div>
</div>
</div>
<div class="columns is-mobile" v-show="country.population">
<div class="column is-4">Population:</div>
<div class="column has-text-right-mobile">
<div v-if="country.population > 1000">
{{ Math.round(country.population / 1000) }}K
</div>
<div v-else>{{ country.population }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<CountryModal
:countryData="countryData"
v-show="isModalVisible"
@close="closeModal"
v-if="countryData"
/>
</div>
</template>
<script>
import CountryModal from "../components/CountryModal";
var html = document.querySelector("html");
export default {
name: "Countries",
components: {
CountryModal,
},
data() {
return {
isModalVisible: false,
countryData: null,
};
},
methods: {
showModal(country) {
this.countryData = country;
this.isModalVisible = true;
html.classList.add("is-clipped");
},
closeModal() {
this.isModalVisible = false;
html.classList.remove("is-clipped");
this.countryData = "";
},
},
computed: {
getCountries() {
return this.$store.getters.allCountries;
},
countries() {
return this.$store.state.countries;
},
},
mounted() {
this.$store.dispatch("getCountries");
},
};
</script>
<style >
.card {
background-color: bisque;
cursor: pointer;
}
</style>
+75
View File
@@ -0,0 +1,75 @@
import actions from '../src/store/actions';
import getters from '../src/store/getters';
import mutations from '../src/store/mutations';
import trueState from '../src/store/state';
const countries = [
{
name: 'Colombia',
capital: 'Bogotá',
},
{
name: 'Romania',
capital: 'Bucharest',
},
];
const state = { countries };
let url = '';
let mockError = false;
jest.mock('axios', () => ({
get: (_url) => {
return new Promise((resolve) => {
if (mockError) throw Error();
url = _url;
resolve(true);
});
},
}));
const commit = jest.fn();
commit.mockReturnValue('SET_COUNTRIES', countries);
describe('actions', () => {
actions.getCountries({ commit });
it('url check', () => {
expect.assertions(2);
expect(url).toBeDefined();
expect(url).toBe('https://restcountries.eu/rest/v2/all');
});
it('commit', () => {
expect.assertions(2);
expect(commit).toBeCalled();
expect(commit).toHaveBeenCalledTimes(1);
});
});
describe('getters', () => {
const testGetters = getters.allCountries(state);
it('returns countries', () => {
expect.assertions(2);
expect(testGetters).toBeDefined();
expect(testGetters).toEqual(state.countries);
});
});
describe('mutations', () => {
it('adds countries to the state', () => {
expect.assertions(2);
mutations.SET_COUNTRIES(state, countries);
expect(mutations).toBeDefined();
expect(state).toEqual({
countries,
});
});
});
describe('state', () => {
it('state exists and has countries', () => {
expect.assertions(3);
expect(trueState).toBeDefined();
expect(trueState).toHaveProperty('countries');
expect(trueState).not.toHaveProperty('cats');
});
});
+5
View File
@@ -0,0 +1,5 @@
describe('Component.js', () => {
test('smoke test', () => {
expect(true).toBe(true) // will obviously always pass
})
})
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
publicPath: process.env.NODE_ENV === 'production' ? '/worldcountries/' : '/',
};