Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion public/api/vehicle_xe.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "xe",
"description": "The most advanced, efficient and refined sports saloon that Jaguar has ever produced",
"description": "The most advanced, efficient and refined sports saloon car.",
"price": "£30,000",
"meta": {
"passengers": 5,
Expand Down
22 changes: 21 additions & 1 deletion src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,25 @@ import { request } from './helpers';
*/
// TODO: All API related logic should be made inside this function.
export default async function getData() {
return [];
const vehicleResponse = await fetch('/api/vehicles.json');

const vehiclesData = await vehicleResponse.json();
const filteredVehicleData = await Promise.all(
vehiclesData.map(async (vehicle) => {
let result = null;
try {
const response = await fetch(vehicle.apiUrl);
result = {
...await response.json(),
...vehicle
};
} catch (error) {
// eslint-disable-next-line
console.log("error: ", error);
}
return result;
})
);

return filteredVehicleData.filter((vehicle) => vehicle && vehicle.price);
}
57 changes: 57 additions & 0 deletions src/components/Modal/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React, { useEffect } from 'react';
import './style.scss';

const Modal = ({ modalContent, setModalContent }) => {
useEffect(() => {
const activeElementList = document.querySelectorAll(
'.modal button, .modal input'
);
const first = activeElementList[0];
const last = activeElementList[activeElementList.length - 1];
first.focus();

const smartModalListener = (e) => {
if (e.key === 'Tab' && e.shiftKey && e.target === first) {
last.focus();
e.preventDefault();
} else if (e.key === 'Tab' && e.target === last && !e.shiftKey) {
first.focus();
e.preventDefault();
}
};
window.addEventListener('keydown', smartModalListener);
return () => {
window.removeEventListener('keydown', smartModalListener);
};
}, []);
return (
<div className="modal">
<div className="modal__window">
<div className="modal__title">
<div className="modal__title-content">Extra Info</div>
<button
type="button"
className="modal__close"
onClick={() => setModalContent(null)}
tabIndex="0"
>
&times;
</button>
</div>
<div className="modal__content">
<span>{modalContent.id.toUpperCase()}</span>
<p>{modalContent.description}</p>
<input type="text" tabIndex="0" />
<p>{modalContent.meta.emissions.template.replace('$value', modalContent.meta.emissions.value)}</p>
<p>{modalContent.price}</p>
</div>
<div className="modal__footer">
<button type="submit" className="modal__button" tabIndex="0">OK</button>
</div>
</div>

</div>
);
};

export default Modal;
23 changes: 23 additions & 0 deletions src/components/Modal/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.modal {
align-items: center;
background-color: rgba($color: #000000, $alpha: .25);
bottom: 0;
display: flex;
justify-content: center;
left: 0;
position: fixed;
right: 0;
text-align: center;
top: 0;

&__title {
display: flex;
justify-content: space-between;
}

&__window {
background-color: white;
border-radius: 10px;
padding: 15px;
}
}
40 changes: 40 additions & 0 deletions src/components/Vehicle/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';

import './style.scss';

const Vehicle = ({ data, setModalContent }) => {
const {
media, id, price, description
} = data;

return (
<div className="vehicle__wrapper">
<div
className="vehicle__thumbnail--mobile"
style={{
backgroundImage: `url('${media[1].url}')`,
}}
/>
<div
className="vehicle__thumbnail--tablet"
style={{
backgroundImage: `url('${media[0].url}')`,
}}
/>

<div className="vehicle__content">
<div className="vehicle__title">
<span>{id}</span>
</div>
<div className="vehicle__price">
From
{price}
</div>
<div className="vehicle__description">{description}</div>
<button type="button" onClick={() => setModalContent(data)} tabIndex="0">Read More</button>
</div>
</div>
);
};

export default Vehicle;
107 changes: 107 additions & 0 deletions src/components/Vehicle/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Mobile
.vehicle {

&__title {
font-size: 17px;
font-weight: bold;
text-align: center;
text-transform: uppercase;
}

&__price {
font-size: 16px;
font-weight: bold;
text-align: center;
}

&__wrapper {
border-bottom: 2px solid #eef1f1;
display: grid;
grid-template-columns: 1fr 4fr;
position: relative;
transition: 3s ease-in;
}

&__description {
font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS",
sans-serif;
font-size: 15px;
text-align: center;
}

&__content {
font-size: 14px;
padding: 9px 18px;
}

&__thumbnail--mobile {
background-repeat: no-repeat;
background-size: contain;
padding-top: 100%;
}

&__thumbnail--tablet,
&__thumbnail--desktop {
background-size: cover;
padding-top: 56.25%;
}
}

// Tablet
@media screen and (min-width: 768px) {

.vehicle {

&__thumbnail--mobile {
display: none;
}

&__wrapper {
grid-template-columns: 1fr;
}

&__title {
color: #0c121c;
margin-top: 10px;

& > span {
border-bottom: 2px solid #0c121c;
border-top: 2px solid #0c121c;
}
}

&__price,
&__description {
color: rgb(139, 139, 139);
font-weight: 200;
}

&__title,
&__price,
&__description {
height: 35px;
text-align: center;
}
}
}

@media screen and (max-width: 767px) {

.vehicle {

&__thumbnail--tablet,
&__thumbnail--desktop {
display: none;
}
}
}

@media screen and (min-width: 1024px) {

.vehicle {

&__wrapper {
border: 0;
}
}
}
45 changes: 19 additions & 26 deletions src/components/VehicleList/index.js
Original file line number Diff line number Diff line change
@@ -1,40 +1,33 @@
import React from 'react';
import React, { useState } from 'react';
import useData from './useData';
import './style.scss';
import Vehicle from '../Vehicle';
import Modal from '../Modal';

export default function VehicleList() {
const VehicleList = () => {
const [modalContent, setModalContent] = useState(false);
// eslint-disable-next-line no-unused-vars
const [loading, error, vehicles] = useData();

// eslint-disable-next-line
console.log("loading, error, vehicles");
// eslint-disable-next-line
console.log(loading, error, vehicles);
if (loading) {
return <div data-testid="loading">Loading</div>;
}

if (error) {
return <div data-testid="error">{ error }</div>;
return <div data-testid="error">{error}</div>;
}

return (
<div data-testid="results">
<p>List of vehicles will be displayed here</p>
<p>
Visit
<a href="/api/vehicles.json" target="_blank"> /api/vehicles.json</a>
{' '}
(main endpoint)
</p>
<p>
Visit
<a href="/api/vehicle_fpace.json" target="_blank">/api/vehicle_fpace.json</a>
{' '}
(detail endpoint - apiUrl)
</p>
<p>
Visit
<a href="/api/vehicle_xf.json" target="_blank">/api/vehicle_xf.json</a>
{' '}
(vehicle without any price)
</p>
</div>
<>
<div className="VehicleList" data-testid="results">
{vehicles.map((car) => <Vehicle key={car.id} data={car} setModalContent={setModalContent} />)}
</div>
{modalContent && <Modal setModalContent={setModalContent} modalContent={modalContent} />}
</>
);
}
};

export default VehicleList;
17 changes: 17 additions & 0 deletions src/components/VehicleList/style.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
.VehicleList {
display: grid;
width: 100%;
}


@media screen and (min-width: 768px) {

.VehicleList {
grid-template-columns: repeat(2, 1fr);
}
}

@media screen and (min-width: 1024px) {

.VehicleList {
grid-template-columns: repeat(4, 1fr);
}
}

6 changes: 1 addition & 5 deletions src/components/VehicleList/useData.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,5 @@ export default function useData() {
.finally(() => setLoading(false));
}, []);

return [
loading,
error,
vehicles,
];
return [loading, error, vehicles];
}
8 changes: 4 additions & 4 deletions src/global-styles.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.root {
margin: 14px;
padding: 12px;
z-index: 12;
html,
body {
font-family: "Gill Sans", "Gill Sans MT", Calibri, "Trebuchet MS", sans-serif;
margin: 0;
}
Loading