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 assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import '../css/app.css';
import Home from './components/Home';
import Home from './layout/Home';

ReactDOM.render(<Router><Home /></Router>, document.getElementById('root'));

8 changes: 8 additions & 0 deletions assets/js/components/BackgroundBox.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from 'react';
export default ({ children }) => {
return (
<div className="bg-light rounded shadow m-2 p-4 container">
{children}
</div>
);
};
17 changes: 17 additions & 0 deletions assets/js/components/ExchangeRateItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from "react";
export default ({data, primary}) => {
return (
<div className={`card ${primary ? 'border-primary' : ''}`} style={{marginBottom: '1rem'}}>
<div className="card-body">
<h5 className="card-title">{data.name} ({data.code})</h5>
<p className="card-text">
<strong>Buy Price:</strong> {data.buyPrice ? data.buyPrice.toFixed(2) : 'N/A'} PLN
<br/>
<strong>Sell Price:</strong> {data.sellPrice ? data.sellPrice.toFixed(2) : 'N/A'} PLN
<br/>
<strong>NBP Price:</strong> {data.nbpPrice ? data.nbpPrice.toFixed(4) : 'N/A'} PLN
</p>
</div>
</div>
);
}
31 changes: 31 additions & 0 deletions assets/js/components/ExchangeRatesForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import BackgroundBox from "./BackgroundBox";
import {getAdjustedTodayString} from "../utils/date";

export default ({ onDateChange, value }) => {
const today = getAdjustedTodayString()
const minDate = '2023-01-01';
return (
<BackgroundBox>
<h1>Kurs wymiany walut</h1>
<form>
<div className="form-group">
<label htmlFor="date">Data kursu</label>
<input
id="date"
type="date"
className="form-control"
aria-describedby="dateHelp"
defaultValue={value}
min={minDate}
max={today}
onChange={(e) => onDateChange(e.target.value)}
/>
<small id="dateHelp" className="form-text text-muted">Wybierz datę, dla której kurs chcesz
zobaczyć. Kurs na dziś pojawi się po godzinie 12:00</small>

</div>
</form>
</BackgroundBox>
);
};
6 changes: 6 additions & 0 deletions assets/js/components/Spinner.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from "react";
export default () => (
<div className={'text-center'}>
<span className="fa fa-spin fa-spinner fa-4x"></span>
</div>
);
19 changes: 13 additions & 6 deletions assets/js/components/Home.js → assets/js/layout/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,35 @@

import React, {Component} from 'react';
import {Route, Redirect, Switch, Link} from 'react-router-dom';
import SetupCheck from "./SetupCheck";
import SetupCheck from "../views/SetupCheck";
import ExchangeRates from "../views/ExchangeRates";

class Home extends Component {

render() {
return (
<div>
<nav className="navbar navbar-expand-lg navbar-dark bg-dark">
<nav className="navbar navbar-expand-lg navbar-dark bg-primary">
<Link className={"navbar-brand"} to={"#"}> Telemedi Zadanko </Link>
<div id="navbarText">
<ul className="navbar-nav mr-auto">
<li className="nav-item">
<Link className={"nav-link"} to={"/setup-check"}> React Setup Check </Link>
</li>
<li className="nav-item">
<Link className={"nav-link"} to={"/exchange-rates"}> Kantor </Link>
</li>

</ul>
</div>
</nav>
<Switch>
<Redirect exact from="/" to="/setup-check" />
<Route path="/setup-check" component={SetupCheck} />
</Switch>
<div className="container py-4 my-4">
<Switch>
<Redirect exact from="/" to="/setup-check" />
<Route path="/setup-check" component={SetupCheck} />
<Route path="/exchange-rates" component={ExchangeRates} />
</Switch>
</div>
</div>
)
}
Expand Down
10 changes: 10 additions & 0 deletions assets/js/utils/date.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export function getAdjustedTodayString() {
const now = new Date();
if (now.getHours() >= 12) {
return now.toLocaleDateString('en-CA');
} else {
const yesterday = new Date();
yesterday.setDate(now.getDate() - 1);
return yesterday.toLocaleDateString('en-CA');
}
}
45 changes: 45 additions & 0 deletions assets/js/utils/useExchangeRates.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {useHistory, useLocation} from "react-router-dom";
import {useEffect, useState} from "react";
import {getAdjustedTodayString} from "./date";
import axios from "axios";

const getRates = async (date) =>
axios.get(`/api/exchange-rates/${date}`)

export default () => {
const location = useLocation();
const history = useHistory();
const [rates, setRates] = useState(null);
const [todayRates, setTodayRates] = useState(null);
const params = new URLSearchParams(location.search);
const dateToday = getAdjustedTodayString();
const dateFromUrl = params.get('date') || getAdjustedTodayString();

useEffect( async () => {
const response = await getRates(dateToday);
setTodayRates(response.data)
}, [setTodayRates]);

useEffect(async () => {
setRates(null);
const response = await getRates(dateFromUrl);
setRates(response.data);
handleDateChange(dateFromUrl);
}, [dateFromUrl]);

const handleDateChange = (newDate) => {
const updatedParams = new URLSearchParams(location.search);
updatedParams.set('date', newDate);
history.push({
pathname: location.pathname,
search: updatedParams.toString(),
});
};

return {
date: dateFromUrl,
rates,
todayRates,
handleDateChange,
};
};
36 changes: 36 additions & 0 deletions assets/js/views/ExchangeRates.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import ExchangeRatesForm from "../components/ExchangeRatesForm";
import React from 'react';
import BackgroundBox from "../components/BackgroundBox";
import Spinner from "../components/Spinner";
import useExchangeRates from "../utils/useExchangeRates";
import ExchangeRateItem from "../components/ExchangeRateItem";
export default () => {
const {
date,
rates,
todayRates,
handleDateChange
} = useExchangeRates();
return (
<div>
<ExchangeRatesForm value={date} onDateChange={handleDateChange}/>
<BackgroundBox>
<div className="container">
<div className="row">
<div className="col-md-6">
<h3 className="text-primary">Kurs dla wybranej daty:</h3>
{!rates && <Spinner/>}
{!!rates && rates.map(rate => <ExchangeRateItem primary={true} key={rate.code}
data={rate}/>)}
</div>
<div className="col-md-6">
<h5 className="mt-2">Kurs na dziś:</h5>
{!todayRates && <Spinner/>}
{!!todayRates && todayRates.map(rate => <ExchangeRateItem key={rate.code} data={rate}/>)}
</div>
</div>
</div>
</BackgroundBox>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React, {Component} from 'react';
import axios from 'axios';
import Spinner from "../components/Spinner";

class SetupCheck extends Component {
constructor() {
Expand Down Expand Up @@ -40,9 +41,7 @@ class SetupCheck extends Component {
<h2 className="text-center"><span>This is a test</span> @ Telemedi</h2>

{loading ? (
<div className={'text-center'}>
<span className="fa fa-spin fa-spinner fa-4x"></span>
</div>
<Spinner/>
) : (
<div className={'text-center'}>
{ this.state.setupCheck === true ? (
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"symfony/http-kernel": "^4.4",
"symfony/twig-bundle": "^4.4.30",
"symfony/webpack-encore-bundle": "^1.17",
"symfony/yaml": "^4.4"
"symfony/yaml": "^4.4",
"ext-json": "*"
},
"conflict": {
"symfony/symfony": "*"
Expand Down
3 changes: 3 additions & 0 deletions config/routes/annotations.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
controllers:
resource: ../../src/App/Controller/
type: annotation
10 changes: 8 additions & 2 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,11 @@ services:
autowire: true
tags: ['controller.service_arguments']

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
argument_resolver.date:
class: 'App\ValueResolver\DateTimeValueResolver'
tags:
- { name: 'controller.argument_value_resolver', priority: 100 }

App\Controller\ExceptionListener:
tags:
- { name: kernel.event_listener, event: kernel.exception }
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions src/App/Controller/ExceptionListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace App\Controller;

use App\Exception\CurrencyValueNotFoundException;
use App\Exception\DateNotValidException;
use App\ViewModel\ErrorViewModel;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;

class ExceptionListener
{
public function onKernelException(ExceptionEvent $event): void
{
$exception = $event->getThrowable();
$model = new ErrorViewModel($exception->getMessage(), true);
if ($exception instanceof DateNotValidException) {
$this->setResponse($event, $model, 400);
}
if ($exception instanceof CurrencyValueNotFoundException) {
$this->setResponse($event, $model, 404);
}
}

private function setResponse(ExceptionEvent $event, ErrorViewModel $model, int $statusCode): void
{
$event->setResponse(new JsonResponse(
$model,
$statusCode
));
}
}
22 changes: 21 additions & 1 deletion src/App/Controller/ExchangeRatesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,30 @@

namespace App\Controller;

use App\Service\CurrencyPriceViewServiceInterface;
use DateTime;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

/**
* @Route("/api/exchange-rates")
*/
class ExchangeRatesController extends AbstractController
{
/** @var CurrencyPriceViewServiceInterface $currencyPriceViewService */
private $currencyPriceViewService;
public function __construct(
CurrencyPriceViewServiceInterface $currencyPriceViewService
) {
$this->currencyPriceViewService = $currencyPriceViewService;
}


/**
* @Route("/{date}")
*/
public function getExchangeRatesByDate(DateTime $date): JsonResponse {
$result = $this->currencyPriceViewService->getAllCurrencyPricesByDate($date);
return $this->json($result);
}
}
10 changes: 10 additions & 0 deletions src/App/Exception/CurrencyValueNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace App\Exception;

use Exception;

class CurrencyValueNotFoundException extends Exception
{

}
10 changes: 10 additions & 0 deletions src/App/Exception/DateNotValidException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace App\Exception;

use Exception;

class DateNotValidException extends Exception
{

}
10 changes: 10 additions & 0 deletions src/App/Exception/InvalidPriceCommissionRateException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace App\Exception;

use Exception;

class InvalidPriceCommissionRateException extends Exception
{

}
Loading