Compare commits

145 Commits

Author SHA1 Message Date
1c62538dca Fix: Footer 2025-08-09 15:40:58 +02:00
5bd5a14f15 Fix: typo 2025-08-09 15:39:52 +02:00
ccea75c99c Update: Paypal code 2025-08-09 15:30:31 +02:00
88a7759aec Update: documentation 2025-08-09 14:56:47 +02:00
36e960ef63 Update: documentation and paypal number 2025-08-09 14:40:28 +02:00
b910f70462 Update: all versions of npm packages 2025-08-09 11:19:56 +02:00
5174053ffd Simplify 2024-04-18 19:06:54 +02:00
601099301c remove php matomo 2024-04-16 19:20:59 +02:00
53db256244 Test matomo 2024-04-16 18:49:59 +02:00
bd4088f949 Compress assets 2024-04-16 06:13:40 +02:00
1427c0d9cc Sitemap generator with default sitemap 2024-04-15 15:42:59 +02:00
5488559bc9 Fix alt images 2024-04-15 15:18:30 +02:00
12279da613 h1 title 2024-04-15 06:43:19 +02:00
424e62b2c5 fix on mobil phone width 2024-04-05 19:00:44 +02:00
0d5c63ebee Fixed width on search 2024-04-05 18:53:46 +02:00
6e1807d678 fix EUR currency 2024-04-01 20:09:48 +02:00
556dc38aa9 Fix typo in code 2024-04-01 20:01:48 +02:00
c11f10817d Enable debug 2024-04-01 19:55:48 +02:00
8e9dbe4ba1 Fix currency 2024-04-01 19:51:15 +02:00
ebaf5e4aee set default currency 2024-04-01 19:18:15 +02:00
0bb757abab Fix country and currency selection 2024-04-01 19:00:57 +02:00
ff4b802d00 Add basic tracking again 2024-04-01 18:10:23 +02:00
c84d007125 Matomo remove 2024-04-01 18:09:37 +02:00
5725307213 Test matomo tracker 2024-04-01 18:02:39 +02:00
c8775f0449 Fix bugs, vue-json-pretty package 2024-03-31 17:33:47 +02:00
95657481d5 Fix typo 2024-03-31 14:46:55 +02:00
fb9d5777ed fix typo 2024-03-31 14:41:01 +02:00
0dc53adc28 Uperrcase remove 2024-03-31 14:31:22 +02:00
5afbe54268 Add Superuser Logging Dashboard 2024-03-31 13:07:25 +02:00
aa7020c19d log referer and ip 2024-03-30 08:04:36 +01:00
93b5a8d00c seo optimization 2024-03-30 07:49:24 +01:00
ec90bcecb6 Price with currency 2024-03-30 07:22:29 +01:00
2d2f639e36 remove console debug, timezone fix, color of text, about text, center tables 2024-03-30 07:02:34 +01:00
51f600f2c0 basic db logging 2024-03-29 15:38:40 +01:00
be27833716 Chnage to local repo for flowbite-vue 2024-03-29 14:58:29 +01:00
acddfa011c Fix size of map on mobile devices 2024-03-29 14:07:32 +01:00
bbb9856c64 change footer text color 2024-03-29 13:55:21 +01:00
da6159861d Add documentation 2024-03-29 12:52:15 +01:00
4aca08d89a Fix matomo tracking url 2024-03-28 05:57:44 +01:00
c8ed8c2abe matomo for vue and basic tracking 2024-03-28 05:49:49 +01:00
19c096bcf6 Change color of map, change size of map on mobile, add favicon 2024-03-27 21:16:00 +01:00
f493d644a5 Search with image and dark theme 2024-03-27 06:33:06 +01:00
b616b11906 Fix footer margin 2024-03-27 06:11:40 +01:00
0708e91705 Fix width of map 2024-03-27 06:07:09 +01:00
5377bdf0a6 Add Matomo 2024-03-26 19:10:11 +01:00
91874d1288 Change of logo 2024-03-26 19:05:59 +01:00
4503b7c33f Map of product count 2024-03-25 19:20:28 +01:00
21865a35d9 Fix rate calculation 2024-03-25 18:36:15 +01:00
fcf0ef36ab database structure fix 2024-03-24 18:09:35 +01:00
b60c4ae88c Bulk of fixes 2024-03-24 17:04:36 +01:00
29f1191afa Fix search code 2024-03-24 15:44:15 +01:00
c9215f456d Fix color of footer 2024-03-24 15:27:50 +01:00
292d7d8ced Online search warnings 2024-03-24 15:22:52 +01:00
3e27d8b650 Footer fixes 2024-03-24 15:09:19 +01:00
6947e5ca75 Fix bug on select currency 2024-03-24 13:53:27 +01:00
c268106579 Placeholde of search 2024-03-24 13:49:26 +01:00
9891ae452b donate button 2024-03-24 13:38:39 +01:00
71d44dd1f9 color change of selected rows 2024-03-23 15:25:54 +01:00
6123a5532a Online search 2024-03-22 11:02:04 +01:00
5aa4055be3 Fix complex query 2024-03-21 21:11:17 +01:00
65720e5433 Fix search country limit 2024-03-21 21:01:09 +01:00
103daee7fb fix length win search 2024-03-21 10:11:20 +01:00
ea241bc5dc Search on all fields, remove search in 2024-03-21 08:47:30 +01:00
673cd019f9 fix hower color on selected row 2024-03-20 20:30:39 +01:00
245178f0d7 selected row, colored 2024-03-20 20:17:07 +01:00
f78bedc2a6 Fix multiple search countries 2024-03-19 20:41:34 +01:00
46496d7438 fix currency recalc 2024-03-19 20:18:54 +01:00
08cadfcf33 fix deps 2024-03-19 19:34:43 +01:00
4645db69dd fix deploy 2024-03-19 19:26:31 +01:00
0a8f7afe3e Redesign with toolbar 2024-03-19 19:21:52 +01:00
56ae6196a5 hide popup from showing 2024-03-07 19:49:53 +01:00
872082ea4a Fix pacakge flowbite 2024-03-07 14:13:29 +01:00
67131615c3 Add image popup, price to float in search 2024-03-07 14:10:11 +01:00
7951052ce3 fix 2024-02-16 17:27:20 +01:00
d551657d99 fix for null length 2024-02-16 17:24:53 +01:00
6e94d5e2d5 Optimize results for mobile phone 2024-02-16 17:16:56 +01:00
fa7482cd6a Small fixes 2024-02-13 20:33:45 +01:00
a8a3cc2b72 Remove table footer 2024-02-13 19:23:10 +01:00
c36aef9819 Small Fixes 2024-02-13 19:12:52 +01:00
4a51269712 Bug in process Request 2024-02-13 06:06:14 +01:00
e6c70e20b9 Mobil visibility fix 2024-02-11 21:15:18 +01:00
4050f47bf3 Test NUC 2024-02-10 22:37:52 +01:00
0f9b1657b4 Map width change 2024-02-10 14:49:24 +01:00
e91f39c9e5 test for nuc 2024-02-10 13:49:36 +01:00
bc7f73c3a0 IS test 2024-02-08 20:43:56 +01:00
602acc14d8 Add conditions if online search 2024-02-04 19:59:55 +01:00
5b39dc1fcc Remove duplicity 2024-02-04 19:37:36 +01:00
354ac8a242 Basic online search with bugs... 2024-02-04 19:35:22 +01:00
d1f5810635 Fix of bugs 2024-01-29 06:17:06 +01:00
efd202a90b Add Long Desc filter and icon 2024-01-28 08:30:45 +01:00
22438b6ef5 Syntax error 2024-01-26 21:50:34 +01:00
245b85b616 Display LAST_REFRESH_TIME 2024-01-26 21:46:16 +01:00
84504ada6a Fix 2024-01-26 21:39:40 +01:00
d90591d501 fix of primary key 2024-01-26 21:34:09 +01:00
88841ad738 Fix of routing 2024-01-26 21:31:06 +01:00
32806e07bd Fix basic settings 2024-01-26 21:23:40 +01:00
17432fd705 Settings basics 2024-01-26 21:19:49 +01:00
5c6f2dd117 Fix messages in mail 2024-01-26 20:42:52 +01:00
dec5d82187 Mail send fix 2024-01-26 20:14:43 +01:00
1755f4cb38 fix of fix 2024-01-25 17:37:51 +01:00
ac6e665bb3 Fix of Fix 2024-01-25 14:47:23 +01:00
ea52e63fc6 Fix ref 2024-01-25 14:38:48 +01:00
0243049cf7 Inform about Email sending... 2024-01-25 14:34:36 +01:00
fb120b5239 Fix send email 2024-01-25 14:02:44 +01:00
7536e54f4a Add mail address for feedback mail 2024-01-25 13:54:47 +01:00
ad8ca024ad Fix Mail template 2024-01-24 22:56:02 +01:00
e561b1f342 Mailer a search in LognDesc 2024-01-24 22:47:44 +01:00
d549e1aaa4 mailing config 2024-01-24 19:23:49 +01:00
cee241604d vee-validate and yup 2024-01-24 18:14:03 +01:00
41dd646e86 Basic feedback mail 2024-01-23 19:23:43 +01:00
ebfa8b47f3 Basic contact form 2024-01-21 19:18:15 +01:00
1b96716059 Rows per page remove 2024-01-21 13:24:14 +01:00
a95f373971 Small redesign 2024-01-21 13:14:12 +01:00
503349d9d5 Fix color, sort order 2024-01-21 10:17:20 +01:00
bfa24de384 fix color on even-row in results 2024-01-21 09:34:56 +01:00
f0fb5232b9 Taskfile ssh 2024-01-21 08:49:00 +01:00
79b550ae3f Colored row in results 2024-01-21 08:33:32 +01:00
5c3e2b89b8 Bug in tproducts 2024-01-20 22:40:38 +01:00
8e3cdf8b17 Test 2024-01-20 21:41:32 +01:00
549e05aeef Weird 2024-01-20 21:37:17 +01:00
f96a7dc159 Test Fix tproducts 2024-01-20 21:25:49 +01:00
c3857fce68 Fix Geoip controller 2024-01-20 20:49:28 +01:00
d0208384ad Log Geoip 2024-01-20 20:28:53 +01:00
bf857c3652 fix geoip 2024-01-20 20:25:39 +01:00
91f7408244 Select country in product search by geoip 2024-01-20 20:13:14 +01:00
dc50d84d48 GeoIP add 2024-01-19 19:15:14 +01:00
670ae0c5e1 Exchange rates move 2024-01-17 20:48:27 +01:00
74a57800ed Filter on Units 2024-01-13 18:13:18 +01:00
80209474fa Deploy to BH 2024-01-13 15:51:26 +01:00
5d9038e845 Add Desc Filter 2024-01-12 18:25:31 +01:00
c59111d15d Fix: local price soring, filtering 2024-01-12 17:49:57 +01:00
b95794c738 Fix sorting 2024-01-12 16:53:31 +01:00
d7e74f5e71 Add tag to results 2024-01-10 21:43:49 +01:00
84ce71a915 search by code fix 2024-01-10 19:32:09 +01:00
416ce7964d resaults table redesign 2024-01-10 19:06:19 +01:00
64c0b726ea Result bugfix 2024-01-10 18:36:17 +01:00
9607fc9dd5 Restult table - mod 2024-01-10 18:13:04 +01:00
b4a9900efc Design cleanup 2024-01-08 20:57:45 +01:00
e32f6c2330 Add Long Desc 2024-01-08 20:37:46 +01:00
90f99cdbdd Add priceUnit and salesPrice columns 2024-01-08 20:14:12 +01:00
183339e583 Translate to english 2024-01-07 09:23:27 +01:00
691e9b8841 Currency recalc, design 2024-01-07 09:18:07 +01:00
23f69817c0 fix mobil clients 2024-01-06 17:59:17 +01:00
4db52ee3c1 deploy newlook branch 2024-01-06 17:10:29 +01:00
e4eec833a9 Redesign 2024-01-06 17:08:33 +01:00
59 changed files with 6590 additions and 1107 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -16,3 +16,14 @@ tasks:
build:
cmds:
- vendor/bin/dep build
ikeaweb01:
cmds:
- vendor/bin/dep deploy web01.ttx.sk
- vendor/bin/dep build web01.ttx.sk
nuc:
cmds:
- vendor/bin/dep deploy nuc.bh.ttx.sk
- vendor/bin/dep build nuc.bh.ttx.sk
ssh:
cmds:
- vendor/bin/dep ssh

View File

@@ -13,14 +13,14 @@ class IkeaPrices extends Command
* @var string
*/
protected $signature = 'ikea:prices {article}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get Ikea Prices';
/**
* Execute the console command.
*
@@ -30,9 +30,9 @@ class IkeaPrices extends Command
{
$article = $this->argument('article');
$responses = $countryCompareController->makeRequests($article);
$prices = $countryCompareController->processResponse($responses,$article);
//dd($prices);
return 0;
$products = $countryCompareController->processResponse($responses,$article);
dd($products);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Spatie\Sitemap\SitemapGenerator;
use Spatie\Crawler\Crawler;
use Spatie\Sitemap\Tags\Url;
use SevenSpan\Matomo\Facades\Matomo;
class IkeaSitemap extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'ikea:sitemap {path}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Command description';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$path = $this->argument('path');
SitemapGenerator::create('https://prices.soson.eu')
->configureCrawler(function (Crawler $crawler) {
$crawler->ignoreRobots();
})
->getSitemap()
->add(Url::create('https://prices.soson.eu/about/')->setPriority(0.7))
->add(Url::create('https://prices.soson.eu/exchange/')->setPriority(0.5))
->add(Url::create('https://prices.soson.eu/doc/')->setPriority(0.8))
->writeToFile($path);
return 0;
}
}

View File

@@ -2,25 +2,31 @@
namespace App\Http\Controllers;
use DOMDocument;
use DOMXPath;
use App\Models\CountryCode;
use Illuminate\Http\Request;
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use PHPHtmlParser\Dom;
class CountryCompareController extends Controller
{
private $countries = [];
private $cHash = [];
private $dom = null;
public function __construct()
{
$countries = [];
CountryCode::all()->each(function ($country) use (&$countries) {
$countries[] = $country->country_name;
$cHash = [];
CountryCode::active('Y')->each(function ($country) use (&$countries, &$cHash) {
$countries[] = $country;
$cHash[$country->country_name] = $country->country_code;
});
$this->countries = collect($countries);
$this->cHash = collect($cHash);
$this->dom = new DOMDocument();
}
public function search(Request $request)
{
@@ -28,16 +34,20 @@ class CountryCompareController extends Controller
$ta_codes = explode(' ', str_replace('.', '', trim($itemCodes)));
$requests = $this->makeRequests($ta_codes[0]);
return $this->processResponse($requests,$ta_codes[0]);
return $this->processResponse($requests, $ta_codes[0]);
}
public function compare($countries, $cHash, $aCodes)
{
$requests = $this->makeRequests($aCodes);
return $this->processResponse($requests, $aCodes);
}
public function makeRequests($ta_code)
{
$requests = Http::pool(fn (Pool $pool) => [
CountryCode::all()->each(function ($countryCode) use ($pool, $ta_code) {
$this->countries[] = $countryCode->country_name;
Log::info('Getting page {url}', [ 'url' => $countryCode->search_url . $ta_code ] );
return $pool->as($countryCode->country_name)->get($countryCode->search_url . $ta_code);
$this->countries->each(function ($country) use ($pool, $ta_code) {
Log::info('Getting page {url}', ['url' => $country->search_url . $ta_code[0]]);
return $pool->as($country->country_code)->withUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36')->get($country->search_url . $ta_code[0]);
})
]);
return $requests;
@@ -45,52 +55,64 @@ class CountryCompareController extends Controller
public function makeRequest($ta_code, $country)
{
$countryCode = CountryCode::where('country_name', $country)->first();
$country = CountryCode::where('country_name', $country)->first();
return Http::pool(fn (Pool $pool) => [
$pool->as($countryCode->country_name)->get($countryCode->search_url . $ta_code)
$pool->as($country->country_name)->get($country->search_url . $ta_code)
]);
}
public function processResponse($responses, $code)
{
$code = $code[0];
$prices = [];
foreach ($this->countries as $country) {
if (isset($responses[$country]) && $responses[$country]->ok()) {
foreach ($this->countries as $c) {
$response = $responses[$country]->body();
$country = $c->country_code;
if ($responses[$country] instanceof \Illuminate\Http\Client\Response && $responses[$country]->ok()) {
// dd($responses);
$response = (string) $responses[$country]->body();
//dd($response)
switch ($country) {
case "Bulgaria":
case "Cyprus":
case "Greece":
$prices[$country] = $this->parseJson_CY_GR_BG($code, (string) $response);
case "BG":
case "CY":
case "GR":
$prices[$country] = $this->parseJson_CY_GR_BG($code, (string) $response, $country);
break;
case "Iceland":
$prices[$country] = $this->parseJson_IS($country, $code, (string) $response);
case "IS":
$prices[$country] = $this->parseJson_IS($code, (string) $response, $country);
break;
case "Türkiye":
$prices[$country] = $this->parseJson_TR((string) $response);
case "TR":
$prices[$country] = $this->parseJson_TR($code, (string) $response, $country);
break;
case "Lithuania":
case "Estonia":
case "Latvia":
$prices[$country] = $this->parseJson_EE_LT_LV($country, $code, (string) $response);
case "EE":
case "LT":
case "LV":
$prices[$country] = $this->parseJson_EE_LT_LV($code, (string) $response, $country);
break;
default:
$prices[$country] = $this->parseJson((string) $response);
$prices[$country] = $this->parseJson($code, (string) $response, $country);
}
}
}
return $prices;
$products = [];
collect($prices)->each(function ($price) use (&$products) {
if (count($price))
$products[] = $price;
});
return collect($products);
}
public function parseJson($json_raw)
public function parseJson($code, $json_raw, $country)
{
$json_values = array();
Log::info('{json_raw} {country}', [$json_raw, $country]);
$json_decoded = json_decode($json_raw, true);
//dd($json_decoded);
$searchResultPage = $json_decoded["searchResultPage"];
$products = $searchResultPage["products"];
$main = $products["main"];
@@ -103,10 +125,11 @@ class CountryCompareController extends Controller
$numeral = $salesPrice["numeral"];
$json_values = array(
'url_product' => $product['pipUrl'],
'code' => $code,
'url' => $product['pipUrl'],
'tag' => $product['tag'],
'price' => $numeral,
'price_eur' => 0
'country' => $country,
'salesPrice' => $numeral,
);
break;
}
@@ -114,98 +137,95 @@ class CountryCompareController extends Controller
return $json_values;
}
public function parseJson_TR($body)
public function parseJson_TR($code, $body, $country)
{
//echo "country: " . $country . ", code: " . $code . "<br>";
$dochtml = new \DOMDocument();
@$dochtml->loadHTML('<?xml encoding="UTF-8">' . $body);
$dochtml = new DOMDocument();
@$dochtml->loadHTML($body, LIBXML_NOERROR);
$xpath = new \DOMXpath($dochtml);
@$xpath = new DOMXpath($dochtml);
$price = null;
//$c = ltrim($code, "0");
$json_values = array();
$url_product = $xpath->query('/html/head/meta[(@property="og:url")]/@content')[0]->nodeValue;
$price = $xpath->query('/html/head/meta[(@property="og:price:amount")]/@content')[0]->nodeValue;
@$url_product = $xpath->query('/html/head/meta[(@property="og:url")]/@content')[0]->nodeValue;
@$price = $xpath->query('/html/head/meta[(@property="og:price:amount")]/@content')[0]->nodeValue;
$price = floatval(trim(str_replace('.', '', $price)));
$json_values = array(
'url_product' => $url_product,
'code' => $code,
'url' => $url_product,
'tag' => null,
'price' => floatval($price),
'price_eur' => 0
'salesPrice' => floatval($price),
'country' => $country,
);
return $json_values;
}
public function parseJson_CY_GR_BG($code, $body)
public function parseJson_CY_GR_BG($code, $body, $country)
{
//echo "country: " . $country . ", code: " . $code . "<br>";
//Log::info('{country},{body},{code}',[$country, $body, $code]);
//$body = file_get_contents("https://www.ikea.bg/search-results/?query=80366284");
libxml_use_internal_errors(true);
$dochtml = new DOMDocument();
@$dochtml->loadHTML($body, LIBXML_NOERROR);
$dochtml = new \DOMDocument();
@$dochtml->loadHTML('<?xml encoding="UTF-8">' . $body);
$xpath = new \DOMXpath($dochtml);
@$xpath = new DOMXpath($dochtml);
$price = null;
$c = ltrim($code, "0");
$json_values = array();
try {
$price = $xpath->query('//*/div[(@class="yotpo yotpo-main-widget" and @data-product-id="' . $code . '")]/@data-price')[0]->nodeValue;
$url_product = $xpath->query('//*/div[(@class="yotpo yotpo-main-widget" and @data-product-id="' . $code . '")]/@data-url')[0]->nodeValue;
} catch (\Exception $e) {
return [];
}
@$price = $xpath->query('//*/div[(@class="yotpo yotpo-main-widget" and @data-product-id="' . $code . '")]/@data-price')[0]->nodeValue;
@$url_product = $xpath->query('//*/div[(@class="yotpo yotpo-main-widget" and @data-product-id="' . $code . '")]/@data-url')[0]->nodeValue;
$price = floatval($price);
$json_values = array(
'url_product' => $url_product,
'code' => $code,
'url' => $url_product,
'tag' => null,
'price' => floatval($price),
'price_eur' => 0
'salesPrice' => floatval($price),
'country' => $country,
);
libxml_clear_errors();
return $json_values;
}
public function parseJson_EE_LT_LV($country, $code, $body)
public function parseJson_EE_LT_LV($code, $body, $country)
{
//echo "country: " . $country . ", code: " . $code . "<br>";
$dochtml = new \DOMDocument();
@$dochtml->loadHTML('<?xml encoding="UTF-8">' . $body);
$dochtml = new DOMDocument();
@$dochtml->loadHTML($body);
$xpath = new \DOMXpath($dochtml);
@$xpath = new DOMXpath($dochtml);
$price = null;
$c = ltrim($code, "0");
$json_values = array();
try {
$price = $xpath->query('//*/div[(@class="itemPriceBox" and @data-item="' . $c . '")]//p[@class="itemNormalPrice display-6"]/span')[0]->nodeValue;
@$price = $xpath->query('//*/div[(@class="itemPriceBox" and @data-item="' . $c . '")]//p[@class="itemNormalPrice display-6"]/span')[0]->nodeValue;
if (is_null($price) || empty($price)) {
$price = $xpath->query('//*/div[(@class="itemPriceBox" and @data-item="' . $c . '")]//div[@class="itemBTI display-6"]/span')[0]->nodeValue;
}
if (is_null($price) || empty($price)) {
@$price = $xpath->query('//*/div[(@class="itemPriceBox" and @data-item="' . $c . '")]//div[@class="itemBTI display-6"]/span')[0]->nodeValue;
}
switch ($country) {
case "Estonia":
$url_product = "https://www.ikea.ee" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
break;
case "Lithuania":
$url_product = "https://www.ikea.lt" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
break;
case "Latvia":
$url_product = "https://www.ikea.lv" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
break;
default:
$url_product = null;
}
} catch (\Exception $e) {
return [];
switch ($country) {
case "EE":
@$url_product = "https://www.ikea.ee" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
break;
case "LT":
@$url_product = "https://www.ikea.lt" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
break;
case "LV":
@$url_product = "https://www.ikea.lv" . $xpath->query('//*/div[(@class="card-header")]/a/@href')[0]->nodeValue;
break;
default:
$url_product = null;
}
if (is_null($price) || empty($price)) {
@@ -215,36 +235,36 @@ class CountryCompareController extends Controller
$price = floatval(trim(str_replace(' ', '', str_replace(',', '.', str_replace(array('€'), '', $price)))));
$json_values = array(
'url_product' => $url_product,
//$url_product,
'code' => $code,
'url' => $url_product,
'tag' => null,
'price' => $price,
'price_eur' => 0
'salesPrice' => $price,
'country' => $country,
);
return $json_values;
}
public function parseJson_IS($country, $code, $body)
public function parseJson_IS($code, $body, $country)
{
//echo "country: " . $country . ", code: " . $code . "<br>";
$dochtml = new \DOMDocument();
@$dochtml->loadHTML('<?xml encoding="UTF-8">' . $body);
$dochtml = new DOMDocument();
@$dochtml->loadHTML($body);
$xpath = new \DOMXpath($dochtml);
@$xpath = new DOMXpath($dochtml);
$price = null;
$c = ltrim($code, "0");
$json_values = array();
try {
$price = $xpath->query('//*/div[(@class="itemPriceBox")]//p[@class="itemNormalPrice revamp_price price"]/span/span')[0]->nodeValue;
@$price = $xpath->query('//*/div[(@class="itemPriceBox")]//p[@class="itemNormalPrice revamp_price price"]/span/span')->item(0)->nodeValue;
if (is_null($price) || empty($price)) {
$price = $xpath->query('//*/div[(@class="itemPriceBox")]//p[@class="itemBTI display-6 revamp_price price"]/span/span')[0]->nodeValue;
@$price = $xpath->query('//*/div[(@class="itemPriceBox")]//p[@class="itemBTI display-6 revamp_price price"]/span/span')->item(0)->nodeValue;
}
$url_product = "https://www.ikea.is" . $xpath->query('/html/head/meta[(@property="og:url")]/@content')[0]->nodeValue;
@$url_product = "https://www.ikea.is" . $xpath->query('/html/head/meta[(@property="og:url")]/@content')[0]->nodeValue;
//echo "url_product: " . $url_product . "<br>";
@@ -252,19 +272,17 @@ class CountryCompareController extends Controller
$url_product = null;
}
} catch (\Exception $e) {
return [];
}
$price = floatval(trim(str_replace(' ', '', str_replace(',', '.', str_replace('.', '', str_replace(array('€'), '', $price))))));
$json_values = array(
'url_product' => $url_product,
//$url_product,
'code' => $code,
'url' => $url_product,
'tag' => null,
'price' => $price,
'price_eur' => 0
'salesPrice' => $price,
'country' => $country,
);
return $json_values;
}
}
// { "country": "AT", "code": "50161321", "url": "https://www.ikea.com/at/de/p/hol-aufbewahrungstisch-akazie-50161321/", "name": "HOL", "typeName": "Aufbewahrungstisch", "mainImageUrl": "https://www.ikea.com/at/de/images/products/hol-aufbewahrungstisch-akazie__0104310_pe251255_s5.jpg", "itemNoGlobal": "50161321", "salesPrice": "80.99", "tag": "FAMILY_PRICE", "last_mod": "2023-12-03 16:44:24" },

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Mail\SendFeedbackMail;
use Illuminate\Support\Facades\Mail;
class FeedbackController extends Controller
{
public function sendEmail(Request $request)
{
$name = $request->input('name');
$email = $request->input('email');
$subject = $request->input('subject');
$message = $request->input('message');
$data = ['name' => $name,'email'=> $email,'message'=> $message, 'subject' => $subject ];
Mail::send('mails.feedback', [ 'data' => $data ], function($message) {
$message->to('jaro@ttx.sk', 'Jaroslv Drzik')
->to('marosholub@gmail.com','Maros Holub')
->subject('IKEA Feedback Mail');
});
return response()->json(['success' => 'Send email successfully.']);
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace App\Http\Controllers;
use App\Models\IkeaProducts;
use Illuminate\Http\Request;
class GeoIPController extends Controller
{
public function index(Request $request, $ip = null)
{
return geoip($ip)->getLocation()->toArray();
}
}

View File

@@ -10,8 +10,12 @@ use Illuminate\Support\Facades\Log;
class IkeaProductsController extends Controller
{
public function search(Request $request, string $item, string $country = '')
public function search(Request $request, string $field, string $text, ?string $country = '')
{
return IkeaProducts::search($item, $country);
return IkeaProducts::search($field, $text, $country);
}
public function multi_search(Request $request, string $item, ?string $desc='', ?string $code='', ?string $country = '')
{
return IkeaProducts::search($item,$desc,$code,$country);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers;
use App\Http\Requests\DataTableRequest;
use App\Models\Logging;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Illuminate\Support\Facades\Log;
class LoggingController extends Controller
{
/**
* @return \Illuminate\Http\Response
*/
public function logging()
{
return Inertia::render('Superuser/Activity/Logging');
}
/**
* @param \App\Http\Requests\DataTableRequest $request
* @return \Illuminate\Http\Response
*/
public function records(DataTableRequest $request)
{
$request->validated();
$rec = Logging::today()->select(['*'])->paginate($request->per_page ?: 30);
Log::info("message",["rec" => $rec]);
return $rec;
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
//use Alfrasc\MatomoTracker\Facades\LaravelMatomoTracker;
use Illuminate\Http\Request;
use App\Models\IkeaProducts;
use Illuminate\Http\Client\Pool;
@@ -10,56 +11,67 @@ use Illuminate\Support\Facades\Log;
use App\Models\CountryCode;
use App\Models\CurrencyRates;
use Inertia\Inertia;
use App\Http\Controllers\CountryCompareController;
class ProductsCompareController extends Controller
{
public function compare(Request $request)
{
$codes = $request->input("codes");
$countries = $request->input("countries");
$currency = $request->input("currency");
Log::info("{codes} {countries}", ["codes" => $codes, "countries" => $countries]);
$codes = collect($codes);
$countries = collect($countries);
if ($countries != null && count($countries)) {
$hCountry = CountryCode::countryHash();
$countries = $countries->map(function ($country) use ($hCountry) {
return $hCountry[$country];
});
} else {
$countries = [];
private $countryCompareController;
public function __construct(CountryCompareController $countryCompareController)
{
$this->countryCompareController = $countryCompareController;
}
public function compare(Request $request)
{
$codes = $request->input("codes");
$countries = $request->input("countries");
$online = $request->input("online");
$text = $request->input("text");
$country = $request->input("country");
$cHash = CountryCode::code_countryHash();
if (is_array($codes) == false)
$aCodes = [$codes['code']];
else
$aCodes = $codes->map(function ($code) {
return $code['code'];
});
$vars = ["codes" => $codes, "countries" => $countries, "country" => $country, "online" => $online, "text" => $text, "ip" => $request->ip(), "referer" => $request->headers->get('referer')];
$products = IkeaProducts::whereIn("code", $aCodes);
if (count($countries)) $products->whereIn("country",$countries);
$products = $products->get();
Log::channel('db')->info("{codes} {countries} {country} {online} {text} {ip} {referer}", $vars );
//LaravelMatomoTracker::doTrackPageView('compare:'. $text);
$currencyRates = CurrencyRates::rates2EUR("Y");
if ($currency == "EUR") {
$coef = 1;
} else {
$coef = floatval(CurrencyRates::where('currency',$currency)->first()->rate);
$codes = collect($codes);
$countries = collect($countries);
if ($countries != null && count($countries)) {
$hCountry = CountryCode::countryHash();
$countries = $countries->map(function ($country) use ($hCountry) {
return $hCountry[$country["name"]];
});
} else {
$countries = [];
}
$cHash = CountryCode::code_countryHash();
if (is_array($codes) == false)
$aCodes = $codes;
else
$aCodes = $codes->map(function ($code) {
return $code;
});
if ($online == true) {
Log::info("ONLINE {codes} {countries}", ["codes" => $codes, "countries" => $countries, "online" => $online]);
$products = $this->countryCompareController->compare($countries, $cHash, $codes);
} else {
$products = IkeaProducts::whereIn("itemNoGlobal", $aCodes);
if (count($countries)) $products->whereIn("country", $countries);
$products = $products->get();
}
$products = $products->map(function ($product) use ($cHash) {
$product["countryName"] = $cHash[$product["country"]];
return $product;
});
Log::info("{products}", ["products" => $products]);
return [
'products' => $products,
'countryHash' => $cHash,
];
}
$products = $products->map(function ($product) use ($currencyRates, $coef) {
$product["salesPrice"] = round(floatval(($product["salesPrice"]) / $currencyRates[$product["country"]]) * $coef, 2);
return $product;
});
Log::info("{products}", ["products" => $products]);
return Inertia::render('IkeaRoot', [
'products' => $products,
'countryHash' => $cHash,
]);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use App\Models\ProductsCount;
use Illuminate\Http\Request;
class ProductsCountController extends Controller
{
public function index()
{
return ProductsCount::all();
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace App\Http\Controllers;
use App\Models\Settings;
use Illuminate\Http\Request;
class SettingsController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
return Settings::all()->pluck("value","name")->all();
}
public function get($name)
{
return Settings::get($name);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Mail\Mailables\Address;
use Illuminate\Mail\Mailables\Envelope;
class SendFeedbackMail extends Mailable
{
use Queueable, SerializesModels;
public $data;
/**
* Create a new message instance.
*
* @return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Build the message.
*
* @return $this
*/
public function build()
{
return $this->subject('IKEA Feedback Mail')->cc('')->view('mails.feedback')->with(['data' => $this->data]);
}
}

View File

@@ -28,7 +28,7 @@ class CurrencyRates extends Model
* @var bool
*/
public $incrementing = false;
use HasFactory;
protected function actual(String $status)

View File

@@ -9,6 +9,7 @@ class IkeaProducts extends Model
{
use HasFactory;
protected $table = 't_ikea_products';
/**
@@ -24,23 +25,41 @@ class IkeaProducts extends Model
*/
public $incrementing = false;
protected function search($item,$country = '')
protected function search($field, $text, $country)
{
if (strlen($item) >= 2 && strlen($item) < 5) {
$result = $this->where('name', 'LIKE', $item . '%');
if ($country != '') $result = $result->where('country',$country);
if ((strlen($text) >= 2 && $field == "name") || (strlen($text) > 3 && $field == 'typeName') || (strlen($text) == 8 && $field == 'code')) {
$result = $this->where($field, 'LIKE', '%' . $text . '%');
if ($country != '') {
$result->where('country', $country);
}
return $result->get();
} else if (strlen($item >= 5)) {
$result = $this->where(function ($query) use ($item) { $query->where('typeName', 'LIKE', '%' . $item . '%')->orWhere('name', 'LIKE', '%' . $item . '%'); } );
if ($country != '') $result = $result->where('country',$country);
} else if ($field == "all") {
if (preg_match("/0[0-9]+/", $text)) {
$result = $this->where("code", '=',$text);
$result->where('country', $country);
} else {
if (strlen($text) < 2) return [];
$result = $this;
return $result->get();
$result = $this->where(function ($query) use ($text) {
if (strlen($text) >= 2) $query->where("name", 'LIKE', '%'. $text . '%');
if (strlen($text) > 3) $query->orWhere("typeName", 'LIKE', '%'. $text . '%');
});
if ($country != '') {
$result->where('country', $country);
}
}
return $result->get();
}
return [];
}
protected function multisearch($codes, $countries) {
protected function multisearch($codes, $countries)
{
//$countries = $
// return $this->where('code',$codes)->where('country',$countries);
// return $this->where('code',$codes)->where('country',$countries);
}
}

21
app/Models/Logging.php Normal file
View File

@@ -0,0 +1,21 @@
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Logging extends Model
{
use HasFactory;
/**
* @var string
*/
protected $table = 'log_messages';
protected function today() {
return $this->whereDate('logged_at',Carbon::today());
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class ProductsCount extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 't_products_count';
/**
* The primary key associated with the table.
*
* @var string
*/
//protected $primaryKey = 'name';
/**
* Indicates if the model's ID is auto-incrementing.
*
* @var bool
*/
public $incrementing = false;
use HasFactory;
protected function get()
{
return $this->get();
}
}

40
app/Models/Settings.php Normal file
View File

@@ -0,0 +1,40 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Settings extends Model
{
use HasFactory;
/**
* The table associated with the model.
*
* @var string
*/
protected $table = 'c_settings';
/**
* The primary key associated with the table.
*
* @var string
*/
//protected $primaryKey = 'name';
/**
* Indicates if the model's ID is auto-incrementing.
*
* @var bool
*/
public $incrementing = false;
use HasFactory;
protected function get($name = null)
{
return $this->where('name',$name)->get();
}
}

View File

@@ -16,10 +16,14 @@
"laravel/sanctum": "^2.14.1",
"laravel/tinker": "^2.7",
"masterminds/html5": "^2.8",
"matomo/matomo-php-tracker": "^3.2",
"paquettg/php-html-parser": "^2.2",
"pusher/pusher-php-server": "^7.0",
"spatie/laravel-permission": "^5.5",
"tightenco/ziggy": "^1.0"
"spatie/laravel-sitemap": "^6.4",
"tightenco/ziggy": "^1.0",
"torann/geoip": "^3.0",
"yoeriboven/laravel-log-db": "^1.2"
},
"require-dev": {
"barryvdh/laravel-debugbar": "^3.7",

2295
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -69,7 +69,7 @@ return [
|
*/
'timezone' => 'Asia/Jakarta',
'timezone' => 'Europe/Bratislava',
/*
|--------------------------------------------------------------------------
@@ -198,6 +198,7 @@ return [
App\Providers\JetstreamServiceProvider::class,
\hisorange\BrowserDetect\ServiceProvider::class,
\Torann\GeoIP\GeoIPServiceProvider::class,
],
/*
@@ -213,6 +214,8 @@ return [
'aliases' => Facade::defaultAliases()->merge([
// 'ExampleClass' => App\Example\ExampleClass::class,
'GeoIP' => \Torann\GeoIP\Facades\GeoIP::class,
'Vite' => \Illuminate\Support\Facades\Vite::class,
])->toArray(),
];

165
config/geoip.php Normal file
View File

@@ -0,0 +1,165 @@
<?php
return [
/*
|--------------------------------------------------------------------------
| Logging Configuration
|--------------------------------------------------------------------------
|
| Here you may configure the log settings for when a location is not found
| for the IP provided.
|
*/
'log_failures' => true,
/*
|--------------------------------------------------------------------------
| Include Currency in Results
|--------------------------------------------------------------------------
|
| When enabled the system will do it's best in deciding the user's currency
| by matching their ISO code to a preset list of currencies.
|
*/
'include_currency' => true,
/*
|--------------------------------------------------------------------------
| Default Service
|--------------------------------------------------------------------------
|
| Here you may specify the default storage driver that should be used
| by the framework.
|
| Supported: "maxmind_database", "maxmind_api", "ipapi"
|
*/
'service' => 'ipapi',
/*
|--------------------------------------------------------------------------
| Storage Specific Configuration
|--------------------------------------------------------------------------
|
| Here you may configure as many storage drivers as you wish.
|
*/
'services' => [
'maxmind_database' => [
'class' => \Torann\GeoIP\Services\MaxMindDatabase::class,
'database_path' => storage_path('app/geoip.mmdb'),
'update_url' => sprintf('https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz', env('MAXMIND_LICENSE_KEY')),
'locales' => ['en'],
],
'maxmind_api' => [
'class' => \Torann\GeoIP\Services\MaxMindWebService::class,
'user_id' => env('MAXMIND_USER_ID'),
'license_key' => env('MAXMIND_LICENSE_KEY'),
'locales' => ['en'],
],
'ipapi' => [
'class' => \Torann\GeoIP\Services\IPApi::class,
'secure' => true,
'key' => env('IPAPI_KEY'),
'continent_path' => storage_path('app/continents.json'),
'lang' => 'en',
],
'ipgeolocation' => [
'class' => \Torann\GeoIP\Services\IPGeoLocation::class,
'secure' => true,
'key' => env('IPGEOLOCATION_KEY'),
'continent_path' => storage_path('app/continents.json'),
'lang' => 'en',
],
'ipdata' => [
'class' => \Torann\GeoIP\Services\IPData::class,
'key' => env('IPDATA_API_KEY'),
'secure' => true,
],
'ipfinder' => [
'class' => \Torann\GeoIP\Services\IPFinder::class,
'key' => env('IPFINDER_API_KEY'),
'secure' => true,
'locales' => ['en'],
],
],
/*
|--------------------------------------------------------------------------
| Default Cache Driver
|--------------------------------------------------------------------------
|
| Here you may specify the type of caching that should be used
| by the package.
|
| Options:
|
| all - All location are cached
| some - Cache only the requesting user
| none - Disable cached
|
*/
'cache' => 'all',
/*
|--------------------------------------------------------------------------
| Cache Tags
|--------------------------------------------------------------------------
|
| Cache tags are not supported when using the file or database cache
| drivers in Laravel. This is done so that only locations can be cleared.
|
*/
'cache_tags' => ['torann-geoip-location'],
/*
|--------------------------------------------------------------------------
| Cache Expiration
|--------------------------------------------------------------------------
|
| Define how long cached location are valid.
|
*/
'cache_expires' => 30,
/*
|--------------------------------------------------------------------------
| Default Location
|--------------------------------------------------------------------------
|
| Return when a location is not found.
|
*/
'default_location' => [
'ip' => '127.0.0.0',
'iso_code' => 'US',
'country' => 'United States',
'city' => 'New Haven',
'state' => 'CT',
'state_name' => 'Connecticut',
'postal_code' => '06510',
'lat' => 41.31,
'lon' => -72.92,
'timezone' => 'America/New_York',
'continent' => 'NA',
'default' => true,
'currency' => 'USD',
],
];

View File

@@ -3,6 +3,7 @@
use Monolog\Handler\NullHandler;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\SyslogUdpHandler;
use Yoeriboven\LaravelLogDb\DatabaseLogger;
return [
@@ -117,6 +118,11 @@ return [
'emergency' => [
'path' => storage_path('logs/laravel.log'),
],
'db' => [
'driver' => 'custom',
'via' => DatabaseLogger::class,
],
],
];

View File

@@ -36,9 +36,9 @@ return [
'mailers' => [
'smtp' => [
'transport' => 'smtp',
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
'port' => env('MAIL_PORT', 587),
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
'host' => env('MAIL_HOST', 'localhost'),
'port' => env('MAIL_PORT', 25),
'encryption' => null,
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
'timeout' => null,

29
config/matomotracker.php Normal file
View File

@@ -0,0 +1,29 @@
<?php
return [
/**
* The URL of your Matomo install
*/
'url' => env('MATOMO_URL', 'https://matomo.soson.eu'),
/**
* The id of the site that should be tracked
*/
'idSite' => env('MATOMO_SITE_ID', 3),
/**
* The auth token of your user
*/
'tokenAuth' => env('MATOMO_AUTH_TOKEN', '3a58110f13366a6438f86a17bf6d2d4c'),
/**
* For queuing the tracking you can use custom queue names. Use 'default' if you want to run the queued items within the standard queue.
*/
'queue' => env('MATOMO_QUEUE', 'matomotracker'),
/**
* Optionally set a custom queue connection. Laravel defaults to "sync".
*/
'queueConnection' => env('MATOMO_QUEUE_CONNECTION', 'default'),
];

View File

@@ -0,0 +1,21 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('log_messages', function (Blueprint $table) {
$table->id();
$table->string('level_name');
$table->unsignedSmallInteger('level');
$table->string('message');
$table->dateTime('logged_at');
$table->json('context');
$table->json('extra');
});
}
};

View File

@@ -1,5 +1,6 @@
import:
import:
- recipe/laravel.php
- contrib/php-fpm.php
config:
repository: 'https://git.bh.ttx.sk/jaro/ikea/'
@@ -7,13 +8,26 @@ config:
hosts:
web01.ttx.sk:
remote_user: git
branch: newlook
deploy_path: '/var/www/html/ikea'
nuc.bh.ttx.sk:
remote_user: deployer
branch: newlook
deploy_path: '/var/www/html/ikea'
bin/php: /usr/bin/php81
php_fpm_version: 8.1
php_fpm_service: php81-php-fpm
after:
deploy: php-fpm:reload
tasks:
build:
- cd: "/var/www/html/ikea/current"
- run: "yarn install"
- run: "yarn build"
- run: "pnpm install"
- run: "pnpm run build"
after:
deploy:failed: deploy:unlock

View File

@@ -109,5 +109,38 @@
"referenceerror: currency is not defined": "ReferenceError: currency is not defined",
"typeerror: can't access property \"unshift\", currency.velue is undefined": "TypeError: can't access property \"unshift\", currency.velue is undefined",
"item must be selected": "Item must be selected",
"typeerror: can't access property \"code\", form.country.value is undefined": "TypeError: can't access property \"code\", form.country.value is undefined"
"typeerror: can't access property \"code\", form.country.value is undefined": "TypeError: can't access property \"code\", form.country.value is undefined",
"referenceerror: item is not defined": "ReferenceError: item is not defined",
"error: ziggy error: route 'products.search.multiple' is not in the route list.": "Error: Ziggy error: route 'products.search.multiple' is not in the route list.",
"error: ziggy error: route 'products.msearch' is not in the route list.": "Error: Ziggy error: route 'products.msearch' is not in the route list.",
"error: request failed with status code 404": "Error: Request failed with status code 404",
"error: ziggy error: object passed as 'item' parameter is missing route model binding key 'undefined'.": "Error: Ziggy error: object passed as 'item' parameter is missing route model binding key 'undefined'.",
"error: ziggy error: object passed as 'field' parameter is missing route model binding key 'undefined'.": "Error: Ziggy error: object passed as 'field' parameter is missing route model binding key 'undefined'.",
"referenceerror: assignment to undeclared variable country": "ReferenceError: assignment to undeclared variable country",
"typeerror: can't access property \"map\", ccountry.value is undefined": "TypeError: can't access property \"map\", ccountry.value is undefined",
"referenceerror: priceunit is not defined": "ReferenceError: priceUnit is not defined",
"referenceerror: mainimagealt is not defined": "ReferenceError: mainImageAlt is not defined",
"referenceerror: countrycurrency is not defined": "ReferenceError: countryCurrency is not defined",
"referenceerror: currencycountryrate is not defined": "ReferenceError: currencyCountryRate is not defined",
"referenceerror: ccountry is not defined": "ReferenceError: ccountry is not defined",
"typeerror: invalid assignment to const 'response'": "TypeError: invalid assignment to const 'response'",
"error: request aborted": "Error: Request aborted",
"typeerror: can't access property \"code\", form.country is null": "TypeError: can't access property \"code\", form.country is null",
"error: request failed with status code 504": "Error: Request failed with status code 504",
"referenceerror: geoip is not defined": "ReferenceError: geoip is not defined",
"referenceerror: ccodes is not defined": "ReferenceError: ccodes is not defined",
"referenceerror: items is not defined": "ReferenceError: items is not defined",
"referenceerror: settings is not defined": "ReferenceError: settings is not defined",
"referenceerror: currencyhash is not defined": "ReferenceError: currencyHash is not defined",
"typeerror: can't access property \"code\", (intermediate value).country.country is undefined": "TypeError: can't access property \"code\", (intermediate value).country.country is undefined",
"typeerror: can't access property \"code\", (intermediate value).country[0] is undefined": "TypeError: can't access property \"code\", (intermediate value).country[0] is undefined",
"error: request failed with status code 419": "Error: Request failed with status code 419",
"error: network error": "Error: Network Error",
"error: ziggy error: route 'products.count' is not in the route list.": "Error: Ziggy error: route 'products.count' is not in the route list.",
"doc": "Doc",
"firefox 125": "Firefox 125",
"logs": "Logs",
"typeerror: $matomo.trackevent is not a function": "TypeError: $matomo.trackEvent is not a function",
"referenceerror: $matomo is not defined": "ReferenceError: $matomo is not defined",
"server error": "Server Error"
}

View File

@@ -5,31 +5,44 @@
"build": "vite build"
},
"devDependencies": {
"@inertiajs/inertia": "^0.11.0",
"@inertiajs/inertia": "^0.11.1",
"@inertiajs/inertia-vue3": "^0.6.0",
"@inertiajs/progress": "^0.2.7",
"@tailwindcss/forms": "^0.5.2",
"@tailwindcss/typography": "^0.5.2",
"@vitejs/plugin-vue": "^2.3.3",
"autoprefixer": "^10.4.7",
"axios": "^0.25",
"laravel-vite-plugin": "^0.2.1",
"lodash": "^4.17.19",
"postcss": "^8.4.14",
"tailwindcss": "^3.1.0",
"vite": "^2.9.11",
"vue": "^3.2.31"
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"@vitejs/plugin-vue": "^2.3.4",
"autoprefixer": "^10.4.21",
"axios": "^0.25.0",
"laravel-vite-plugin": "^0.2.4",
"lodash": "^4.17.21",
"postcss": "^8.5.6",
"tailwindcss": "^3.4.17",
"vite": "^2.9.18",
"vite-plugin-compression": "^0.5.1"
},
"dependencies": {
"@vueform/multiselect": "^2.5.1",
"flowbite": "^2.1.1",
"flowbite-vue": "^0.1.0",
"laravel-echo": "^1.13.0",
"pusher-js": "^7.3.0",
"sweetalert2": "^11.4.23",
"@coreui/vue": "^4.10.2",
"@inertiajs/vue3": "^1.3.0",
"@vueform/multiselect": "^2.6.11",
"flag-icons": "^7.5.0",
"flowbite": "^2.5.2",
"flowbite-vue": "git+https://git.bh.ttx.sk/jaro/flowbite-vue",
"laravel-echo": "^1.19.0",
"pusher-js": "^7.6.0",
"sweetalert2": "^11.22.3",
"vee-validate": "^4.15.1",
"vue": "^3.5.18",
"vue-google-charts": "^1.1.0",
"vue-multiselect": "^3.0.0-beta.3",
"vue-json-pretty": "^2.5.0",
"vue-matomo": "^4.2.0",
"vue-multiselect": "^3.3.1",
"vue3-easy-data-table": "^1.5.47",
"vuedraggable": "^4.1.0"
}
"vue3-multiselect-checkboxed": "^0.0.9",
"vue3-popper": "^1.5.0",
"vuedraggable": "^4.1.0",
"yup": "^1.7.0"
},
"bundleDependencies": [
"flowbite-vue"
]
}

2461
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

BIN
public/.DS_Store vendored Normal file

Binary file not shown.

BIN
public/images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

23
public/sitemap.xml Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9">
<url>
<loc>https://prices.soson.eu/</loc>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://prices.soson.eu/about/</loc>
<changefreq>daily</changefreq>
<priority>0.7</priority>
</url>
<url>
<loc>https://prices.soson.eu/exchange/</loc>
<changefreq>daily</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://prices.soson.eu/doc/</loc>
<changefreq>daily</changefreq>
<priority>0.8</priority>
</url>
</urlset>

BIN
resources/.DS_Store vendored Normal file

Binary file not shown.

0
resources/adminer/plugins/.gitignore vendored Normal file
View File

BIN
resources/js/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,56 @@
<template>
<div class="relative">
<button class="z-10 relative flex items-center focus:outline-none select-none" @click="open = !open">
<slot name="button"></slot>
</button>
<!-- to close when clicked on space around it in desktop-->
<button class="fixed inset-0 h-full w-full cursor-default focus:outline-none" v-if="open" @click="open = false" tabindex="-1"></button>
<!--dropdown content: desktop-->
<transition enter-active-class="transition-all duration-200 ease-out" leave-active-class="transition-all duration-750 ease-in" enter-class="opacity-0 scale-75" enter-to-class="opacity-100 scale-100" leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-75">
<div class="hidden md:block absolute shadow-lg border w-48 rounded py-1 px-2 text-sm mt-4 bg-white" :class="placement === 'right' ? 'right-0' : 'left-0'" v-if="open">
<slot name="content"></slot>
</div>
</transition>
<!--dropdown content: mobile-->
<transition enter-active-class="transition-all duration-200 ease-out" leave-active-class="transition-all duration-750 ease-in" enter-class="opacity-0 scale-75" enter-to-class="opacity-100 scale-100" leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-75">
<div class="md:hidden fixed inset-x-0 bottom-0 bg-white w-full z-20 px-2 py-2 shadow-2xl leading-loose" v-if="open">
<slot name="content"></slot>
</div>
</transition>
<!-- to close when clicked on space around it in mobile-->
<div class="md:hidden fixed w-full h-full inset-0 bg-gray-900 opacity-50 z-10" @click="open = false" v-if="open"></div>
</div>
</template>
<script>
export default {
data() {
return {
open: false,
};
},
props: {
placement: {
type: String,
default: "right",
validator: (value) => ["right", "left"].indexOf(value) !== -1,
},
},
mounted() {
const onEscape = (e) => {
if (e.key === "Esc" || e.key === "Escape") {
this.open = false;
}
};
document.addEventListener("keydown", onEscape);
// this.$once("hook:beforeDestroy", () => {
// document.removeEventListener("keydown", onEscape);
// });
},
};
</script>

View File

@@ -1,17 +1,68 @@
<script setup>
import Swal from 'sweetalert2';
import { getCurrentInstance, ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
import { FwbDropdown, FwbListGroup, FwbListGroupItem } from 'flowbite-vue';
import LogoIkea from '@/assets/Ikea_logo.svg';
import { ref, onMounted, computed ,watch } from 'vue';
import LogoIkea from '@/assets/price-tag.svg';
import {
FwbSelect,
FwbNavbar,
FwbNavbarCollapse,
FwbNavbarLink,
FwbNavbarLogo,
FwbButton,
FwbDropdown,
FwbListGroup,
FwbListGroupItem,
FwbMegaMenu,
FwbMegaMenuDropdown,
FwbRadio,
FwbFooter,
FwbFooterCopyright,
FwbFooterLink,
FwbFooterLinkGroup,
} from 'flowbite-vue';
import { Link, usePage } from '@inertiajs/vue3'
const isAuth = computed(() => page.props.auth.user)
import Multiselect from "vue-multiselect";
import { settingsStore } from '../settingsStore.js';
const menu = ref([])
const geoip = ref(null)
const ccodes = ref([])
const rates = ref([])
const currency = ref([])
const countryCurrency = ref([])
const currencyHash = ref([])
const ccountry = ref([])
const ccountry_list = computed(() => {
let computed_list = settingsStore.ccountry_list.filter((c) => (c.offline == (settingsStore.online ? 'N' : 'Y')) || settingsStore.online == true);
console.log('COMPUTED_LIST',computed_list);
return computed_list;
})
const scountry_list = computed(() => {
let computed_list = settingsStore.ccountry_list.filter((c) => (c.offline == (settingsStore.online ? 'N' : 'Y')) || settingsStore.online == true);
console.log('COMPUTED_LIST',computed_list);
return computed_list;
})
let tsettings = computed(() => {
let country = "GB";
console.log('AAAAA',settingsStore.country);
if (settingsStore.country != "") country = settingsStore.country.code;
if (settingsStore.currency != "EUR")
settingsStore.currencyCoef = currencyHash.value[settingsStore.currency];
else settingsStore.currencyCoef = 1.0;
return `${country} / ${settingsStore.currency}`;
});
const fetch_menu = async () => {
try {
const response = await axios.get(route('menu.user'))
@@ -29,32 +80,231 @@ const fetch_menu = async () => {
}
}
const fetch_ccodes = async () => {
try {
const response2 = await axios.get(route("geo.ip.get"));
geoip.value = response2.data;
const response = await axios.get(route("ccountry.codes"));
ccodes.value = response.data;
ccodes.value = Object.entries(ccodes.value)
.map(([k, v]) => {
if (v !== null) return { country: k, code: v };
})
.filter((n) => n);
console.log("ccodes=o", ccodes.value);
} catch (e) {
const response = await Swal.fire({
title: __("are you want to try again") + "?",
text: __(`${e}`),
icon: "question",
showCancelButton: true,
showCloseButton: true,
});
response.isConfirmed && fetch();
}
};
const fetch_rates = async () => {
try {
const response = await axios.get(route("rates.index"));
rates.value = response.data;
currency.value = rates.value.map((currency) => currency.currency);
countryCurrency.value = Object.fromEntries(
rates.value.map((currency) => [currency.country_name, currency.currency])
);
currencyHash.value = Object.fromEntries(
rates.value.map((currency) => [currency.currency, currency.rate])
);
currency.value.unshift("EUR");
console.log("RATE=", rates.value);
console.log("HASH=", currencyHash.value);
} catch (e) {
const response = await Swal.fire({
title: __("are you want to try again") + "?",
text: __(`${e}`),
icon: "question",
showCancelButton: true,
showCloseButton: true,
});
response.isConfirmed && fetch();
}
};
const fetch = async () => {
try {
const response = await axios.get(route("ccountry.active"));
ccountry.value = response.data;
let aCntry = ccountry.value.map((country) => [country.country_name]);
settingsStore.ccountry_filter.push(...aCntry);
console.log('CCOUNTRY=',ccountry);
settingsStore.ccountry_list = ccountry.value.map((country) => {
return {
name: country.country_name,
code: country.country_code,
status: country.status,
offline: country.only_offline,
};
});
} catch (e) {
const response = await Swal.fire({
title: __("are you want to try again") + "?",
text: __(`${e}`),
icon: "question",
showCancelButton: true,
showCloseButton: true,
});
response.isConfirmed && fetch();
}
};
function selectCurrency(item, id) {
if (typeof countryCurrency.value[settingsStore.country.name] !== 'undefined') {
settingsStore.currency = countryCurrency.value[settingsStore.country.name];
} else {
settingsStore.currency = "EUR";
}
if (settingsStore.currency != "EUR") {
settingsStore.currencyCoef = currencyHash.value[settingsStore.currency];
}
else settingsStore.currencyCoef = 1.0;
}
function selectCountry(item, id) {
let cCntry = [["Country"]];
let aCntry = settingsStore.countries.map((country) => [country.name]);
if (settingsStore.countries.length == 0) aCntry = ccountry.value.map((country) => [country.country_name]);
settingsStore.ccountry_filter = cCntry;
settingsStore.ccountry_filter.push(...aCntry);
}
watch(ccodes, (codes) => {
console.log("NNN=", codes);
console.log("IP=", geoip.value);
let found = false;
settingsStore.ccountry_list.forEach((c) => {
if (c.code == geoip.value.iso_code) {
settingsStore.country = c;
found = true;
}
});
if (found == false) settingsStore.country = settingsStore.ccountry_list.filter((c) => {return c.code == 'GB'})[0];
if (typeof countryCurrency.value[settingsStore.country.name] !== 'undefined') {
settingsStore.currency = countryCurrency.value[settingsStore.country.name];
} else {
settingsStore.currency = "EUR";
}
console.log("FC=", settingsStore.country,settingsStore.currency);
});
onMounted(fetch);
onMounted(fetch_rates);
onMounted(fetch_ccodes);
onMounted(fetch_menu);
</script>
<style>
@import "flag-icons/css/flag-icons.min.css";
</style>
<template>
<fwb-navbar>
<fwb-navbar class="px-0">
<template #logo>
<fwb-navbar-logo alt="IKEA Price Craweler" :image-url="LogoIkea" :link="route('root')">
IKEA Price Crawler
<fwb-navbar-logo alt="Logo" :image-url="LogoIkea" :link="route('root')">
<h1>IKEA price comparison</h1>
</fwb-navbar-logo>
</template>
<template #default="{ isShowMenu }">
<fwb-navbar-collapse :is-show-menu="isShowMenu" v-if="menu.length > 0">
<fwb-navbar-link :link="route(menu[0].route_or_url)">
Home
</fwb-navbar-link>
<fwb-navbar-link v-if="$page.props.user" :link="route('profile.show')">
Profile
</fwb-navbar-link>
<fwb-navbar-link v-if="!$page.props.user" :link="route('login')">
Login
</fwb-navbar-link>
<fwb-mega-menu link="#">
<template #default>{{ tsettings }}</template>
</fwb-mega-menu>
<template v-for="item in menu[0].childs" v-if="menu.length > 0">
<fwb-navbar-link :link="route(item.route_or_url)">
{{ item.name }}
</fwb-navbar-link>
</template>
<fwb-navbar-link link="#">
<form action="https://www.paypal.com/donate" method="post" target="_top">
<input type="hidden" name="hosted_button_id" value="3NF26KAZBP7XA" />
<button type="sumbit">Donate</button>
</form>
</fwb-navbar-link>
</fwb-navbar-collapse>
</template>
<template #mega-menu-dropdown>
<fwb-mega-menu-dropdown>
<div class="flex-auto">
<div class="flex flex-col start-0">
<span class="font-extrabold font-mono">Country to search in</span>
</div>
<div class="">
<multiselect @select="selectCurrency" placeholder="All" :close-on-select="true" :multiple="false" v-model="settingsStore.country" :options="scountry_list" label="name" track-by="code">
<template #option="slotProps">
<span :class="'pl-6 fi fi-' + slotProps.option.code.toLowerCase()">{{ slotProps.option.name }}</span>
</template>
</multiselect>
</div>
</div>
<div class="flex-auto">
<div class="flex flex-col start-0">
<span class="font-extrabold font-mono">Countries to compare with</span>
</div>
<div>
<multiselect placeholder="All" @remove="selectCountry" @select="selectCountry" :close-on-select="false" :multiple="true" v-model="settingsStore.countries" :options="ccountry_list" label="name" track-by="name">
<template #option="slotProps">
<span :class="'pl-6 fi fi-' + slotProps.option.code.toLowerCase()">{{ slotProps.option.name }}</span>
</template>
</multiselect>
</div>
</div>
<div class="flex-auto">
<div class="flex flex-col start-0">
<span class="font-extrabold font-mono">Currency recalculation</span>
</div>
<fwb-radio v-for="c in currency" v-model="settingsStore.currency" name="radio" :label="c" :value="c" />
</div>
</fwb-mega-menu-dropdown>
</template>
</fwb-navbar>
<div class="m-3">
<div class="m-3 mb-0 pb-24">
<slot />
</div>
</template>
<fwb-footer class="p-4 text-gray-400 dark:text-gray-400 rounded-none bg-white shadow md:flex md:items-center md:justify-between md:p-6 dark:bg-gray-900 bottom-0 absolute w-full">
All information without guarantee
<fwb-footer-copyright
by="JD & MH Slovakia™"
href="/"
copyright-message=""
/>
</fwb-footer>
</template>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
_o

View File

@@ -0,0 +1,212 @@
<template>
<nav class="bg-white border-gray-200 dark:border-gray-600 dark:bg-gray-900">
<div class="flex flex-wrap justify-between items-center mx-auto max-w-screen-xl p-4">
<a
href="https://flowbite.com"
class="flex items-center space-x-3 rtl:space-x-reverse"
>
<img
src="https://flowbite.com/docs/images/logo.svg"
class="h-8"
alt="Flowbite Logo"
/>
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white"
>Flowbite</span
>
</a>
<button
data-collapse-toggle="mega-menu-full-cta"
type="button"
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
aria-controls="mega-menu-full-cta"
aria-expanded="false"
>
<span class="sr-only">Open main menu</span>
<svg
class="w-5 h-5"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 17 14"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 1h15M1 7h15M1 13h15"
/>
</svg>
</button>
<div
id="mega-menu-full-cta"
class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1"
>
<ul
class="flex flex-col mt-4 font-medium md:flex-row md:mt-0 md:space-x-8 rtl:space-x-reverse"
>
<li>
<a
href="#"
class="block py-2 px-3 text-gray-900 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-blue-500 md:dark:hover:bg-transparent dark:border-gray-700"
aria-current="page"
>Home</a
>
</li>
<li>
<button
id="mega-menu-full-cta-dropdown-button"
data-collapse-toggle="mega-menu-full-cta-dropdown"
data-dropdown-placement="bottom"
class="flex items-center justify-between w-full py-2 px-3 font-medium text-gray-900 border-b border-gray-100 md:w-auto hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-600 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-blue-500 md:dark:hover:bg-transparent dark:border-gray-700"
>
Company
<svg
class="w-2.5 h-2.5 ms-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 10 6"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 1 4 4 4-4"
/>
</svg>
</button>
</li>
<li>
<a
href="#"
class="block py-2 px-3 text-gray-900 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-blue-500 md:dark:hover:bg-transparent dark:border-gray-700"
>Marketplace</a
>
</li>
<li>
<a
href="#"
class="block py-2 px-3 text-gray-900 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-blue-500 md:dark:hover:bg-transparent dark:border-gray-700"
>Resources</a
>
</li>
<li>
<a
href="#"
class="block py-2 px-3 text-gray-900 border-b border-gray-100 hover:bg-gray-50 md:hover:bg-transparent md:border-0 md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-blue-500 md:dark:hover:bg-transparent dark:border-gray-700"
>Contact</a
>
</li>
</ul>
</div>
</div>
<div
id="mega-menu-full-cta-dropdown"
class="mt-1 bg-white border-gray-200 shadow-sm border-y dark:bg-gray-800 dark:border-gray-600"
>
<div
class="grid max-w-screen-xl px-4 py-5 mx-auto text-sm text-gray-500 dark:text-gray-400 md:grid-cols-3 md:px-6"
>
<ul class="space-y-4 sm:mb-4 md:mb-0" aria-labelledby="mega-menu-full-cta-button">
<li>
<a
href="#"
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
>
Online Stores
</a>
</li>
<li>
<a
href="#"
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
>
Segmentation
</a>
</li>
<li>
<a
href="#"
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
>
Marketing CRM
</a>
</li>
<li>
<a
href="#"
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
>
Online Stores
</a>
</li>
</ul>
<ul class="hidden mb-4 space-y-4 md:mb-0 sm:block">
<li>
<a
href="#"
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
>
Our Blog
</a>
</li>
<li>
<a
href="#"
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
>
Terms & Conditions
</a>
</li>
<li>
<a
href="#"
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
>
License
</a>
</li>
<li>
<a
href="#"
class="hover:underline hover:text-blue-600 dark:hover:text-blue-500"
>
Resources
</a>
</li>
</ul>
<div class="mt-4 md:mt-0">
<h2 class="mb-2 font-semibold text-gray-900 dark:text-white">Our brands</h2>
<p class="mb-2 text-gray-500 dark:text-gray-400">
At Flowbite, we have a portfolio of brands that cater to a variety of
preferences.
</p>
<a
href="#"
class="inline-flex items-center text-sm font-medium text-blue-600 hover:underline hover:text-blue-600 dark:text-blue-500 dark:hover:text-blue-700"
>
Explore our brands
<span class="sr-only">Explore our brands </span>
<svg
class="w-3 h-3 ms-2 rtl:rotate-180"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M1 5h12m0 0L9 1m4 4L9 9"
/>
</svg>
</a>
</div>
</div>
</div>
</nav>
</template>

File diff suppressed because one or more lines are too long

View File

@@ -1,34 +1,133 @@
<script setup>
import { getCurrentInstance, ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
import GuestLayout from '../Layouts/GuestLayout.vue';
import { useForm } from '@inertiajs/inertia-vue3'
import axios from 'axios';
import { Form, Field, ErrorMessage } from 'vee-validate';
import * as yup from 'yup';
import { ref } from "vue";
import Swal from "sweetalert2";
const result = ref({ });
const siteKey = ref('6Ld2MF0pAAAAAFpJOfU1FAxr7QiEoq1RJT1Pn2Hp');
const schema = yup.object().shape({
name: yup.string().required('Name is required'),
email: yup.string().email('Email is invalid').required('Email is required'),
subject: yup.string().required('Subject is required'),
message: yup.string().required('Message is required'),
// recaptcha: yup.string().required('Please verify you are human'),
});
const onSubmit = async (data) => {
console.log("DATA=", data);
try {
const response = await axios.post(route("ajax.send.email"), {
name: data.name,
email: data.email,
subject: data.subject,
message: data.message,
});
console.log("TEST=", response.data);
result.value = response.data;
} catch (e) {
console.log('TEST=',`${e}`);
result.value = { 'error': `${e}`};
}
};
</script>
<template>
<GuestLayout>
<div class="flex items-center justify-center h-screen">
<GuestLayout>
<div class="flex items-center justify-center">
<div class="grid sm:grid-cols-2 items-center gap-16 p-8 mx-auto max-w-4xl bg-white shadow-[0_2px_10px_-3px_rgba(6,81,237,0.3)] rounded-md text-[#333] font-[sans-serif]">
<strong>What is Ikea price comparison?</strong>
<p>
It is a community project of a group of enthusiasts from Slovakia.
This service provides price comparison of Ikea products in European countries.
</p>
<p>
Product prices are converted to appropriate currency (you can change it in settings menu) at the current rate and appear in resulting table.
You can go to the appropriate product page by clicking on the country name.
</p>
<div class="bg-indigo-800 text-white font-bold rounded-lg border shadow-lg p-10">
<h1>What is Ikea price comparison?</h1>
<p>
It is a community project of a group of enthusiasts from Slovakia.
This service provides price comparison of Ikea products in European countries.
Product prices are converted to EUR at the current rate and appear in resulting table.
Information about the prices of Ikea products is obtained online.
You can go to the appropriate product page by clicking on the country name.
Thanks to this service, you can find out in which country a given Ikea product is the cheapest (you can sort
them in the resulting table).
</p>
<br />
Enjoy :)
</div>
<p>
Thanks to this service, you can find out in which country a given Ikea product is the cheapest (you can sort them in the resulting table).
</p>
Enjoy :)
</div>
</div>
</GuestLayout>
<div class="my-6">
<div class="grid sm:grid-cols-2 items-center gap-16 p-8 mx-auto max-w-4xl bg-white shadow-[0_2px_10px_-3px_rgba(6,81,237,0.3)] rounded-md text-[#333] font-[sans-serif]">
<div>
<h1 class="text-3xl font-extrabold">Message</h1>
<p class="text-sm text-gray-400 mt-3">If you have some comments or ideas for improvement about project, let as know...</p>
<!-- <div class="mt-12">
<h2 class="text-lg font-extrabold">Email</h2>
<ul class="mt-3">
<li class="flex items-center">
<div class="bg-[#e6e6e6cf] h-10 w-10 rounded-full flex items-center justify-center shrink-0">
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill='#007bff' viewBox="0 0 479.058 479.058">
<path d="M434.146 59.882H44.912C20.146 59.882 0 80.028 0 104.794v269.47c0 24.766 20.146 44.912 44.912 44.912h389.234c24.766 0 44.912-20.146 44.912-44.912v-269.47c0-24.766-20.146-44.912-44.912-44.912zm0 29.941c2.034 0 3.969.422 5.738 1.159L239.529 264.631 39.173 90.982a14.902 14.902 0 0 1 5.738-1.159zm0 299.411H44.912c-8.26 0-14.971-6.71-14.971-14.971V122.615l199.778 173.141c2.822 2.441 6.316 3.655 9.81 3.655s6.988-1.213 9.81-3.655l199.778-173.141v251.649c-.001 8.26-6.711 14.97-14.971 14.97z" data-original="#000000" />
</svg>
</div>
<a target="blank" href="#" class="text-[#007bff] text-sm ml-3">
<small class="block">Mail</small>
<strong>https://gmail.com</strong>
</a>
</li>
</ul>
</div>
<div class="mt-12">
<h2 class="text-lg font-extrabold">Socials</h2>
<ul class="flex mt-3 space-x-4">
<li class="bg-[#e6e6e6cf] h-10 w-10 rounded-full flex items-center justify-center shrink-0">
<a href="javascript:void(0)">
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill='#007bff' viewBox="0 0 24 24">
<path d="M6.812 13.937H9.33v9.312c0 .414.335.75.75.75l4.007.001a.75.75 0 0 0 .75-.75v-9.312h2.387a.75.75 0 0 0 .744-.657l.498-4a.75.75 0 0 0-.744-.843h-2.885c.113-2.471-.435-3.202 1.172-3.202 1.088-.13 2.804.421 2.804-.75V.909a.75.75 0 0 0-.648-.743A26.926 26.926 0 0 0 15.071 0c-7.01 0-5.567 7.772-5.74 8.437H6.812a.75.75 0 0 0-.75.75v4c0 .414.336.75.75.75zm.75-3.999h2.518a.75.75 0 0 0 .75-.75V6.037c0-2.883 1.545-4.536 4.24-4.536.878 0 1.686.043 2.242.087v2.149c-.402.205-3.976-.884-3.976 2.697v2.755c0 .414.336.75.75.75h2.786l-.312 2.5h-2.474a.75.75 0 0 0-.75.75V22.5h-2.505v-9.312a.75.75 0 0 0-.75-.75H7.562z" data-original="#000000" />
</svg>
</a>
</li>
<li class="bg-[#e6e6e6cf] h-10 w-10 rounded-full flex items-center justify-center shrink-0">
<a href="javascript:void(0)">
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill='#007bff' viewBox="0 0 511 512">
<path d="M111.898 160.664H15.5c-8.285 0-15 6.719-15 15V497c0 8.285 6.715 15 15 15h96.398c8.286 0 15-6.715 15-15V175.664c0-8.281-6.714-15-15-15zM96.898 482H30.5V190.664h66.398zM63.703 0C28.852 0 .5 28.352.5 63.195c0 34.852 28.352 63.2 63.203 63.2 34.848 0 63.195-28.352 63.195-63.2C126.898 28.352 98.551 0 63.703 0zm0 96.395c-18.308 0-33.203-14.891-33.203-33.2C30.5 44.891 45.395 30 63.703 30c18.305 0 33.195 14.89 33.195 33.195 0 18.309-14.89 33.2-33.195 33.2zm289.207 62.148c-22.8 0-45.273 5.496-65.398 15.777-.684-7.652-7.11-13.656-14.942-13.656h-96.406c-8.281 0-15 6.719-15 15V497c0 8.285 6.719 15 15 15h96.406c8.285 0 15-6.715 15-15V320.266c0-22.735 18.5-41.23 41.235-41.23 22.734 0 41.226 18.495 41.226 41.23V497c0 8.285 6.719 15 15 15h96.403c8.285 0 15-6.715 15-15V302.066c0-79.14-64.383-143.523-143.524-143.523zM466.434 482h-66.399V320.266c0-39.278-31.953-71.23-71.226-71.23-39.282 0-71.239 31.952-71.239 71.23V482h-66.402V190.664h66.402v11.082c0 5.77 3.309 11.027 8.512 13.524a15.01 15.01 0 0 0 15.875-1.82c20.313-16.294 44.852-24.907 70.953-24.907 62.598 0 113.524 50.926 113.524 113.523zm0 0" data-original="#000000" />
</svg>
</a>
</li>
<li class="bg-[#e6e6e6cf] h-10 w-10 rounded-full flex items-center justify-center shrink-0">
<a href="javascript:void(0)">
<svg xmlns="http://www.w3.org/2000/svg" width="20px" height="20px" fill='#007bff' viewBox="0 0 24 24">
<path d="M12 9.3a2.7 2.7 0 1 0 0 5.4 2.7 2.7 0 0 0 0-5.4Zm0-1.8a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Zm5.85-.225a1.125 1.125 0 1 1-2.25 0 1.125 1.125 0 0 1 2.25 0ZM12 4.8c-2.227 0-2.59.006-3.626.052-.706.034-1.18.128-1.618.299a2.59 2.59 0 0 0-.972.633 2.601 2.601 0 0 0-.634.972c-.17.44-.265.913-.298 1.618C4.805 9.367 4.8 9.714 4.8 12c0 2.227.006 2.59.052 3.626.034.705.128 1.18.298 1.617.153.392.333.674.632.972.303.303.585.484.972.633.445.172.918.267 1.62.3.993.047 1.34.052 3.626.052 2.227 0 2.59-.006 3.626-.052.704-.034 1.178-.128 1.617-.298.39-.152.674-.333.972-.632.304-.303.485-.585.634-.972.171-.444.266-.918.299-1.62.047-.993.052-1.34.052-3.626 0-2.227-.006-2.59-.052-3.626-.034-.704-.128-1.18-.299-1.618a2.619 2.619 0 0 0-.633-.972 2.595 2.595 0 0 0-.972-.634c-.44-.17-.914-.265-1.618-.298-.993-.047-1.34-.052-3.626-.052ZM12 3c2.445 0 2.75.009 3.71.054.958.045 1.61.195 2.185.419A4.388 4.388 0 0 1 19.49 4.51c.457.45.812.994 1.038 1.595.222.573.373 1.227.418 2.185.042.96.054 1.265.054 3.71 0 2.445-.009 2.75-.054 3.71-.045.958-.196 1.61-.419 2.185a4.395 4.395 0 0 1-1.037 1.595 4.44 4.44 0 0 1-1.595 1.038c-.573.222-1.227.373-2.185.418-.96.042-1.265.054-3.71.054-2.445 0-2.75-.009-3.71-.054-.958-.045-1.61-.196-2.185-.419A4.402 4.402 0 0 1 4.51 19.49a4.414 4.414 0 0 1-1.037-1.595c-.224-.573-.374-1.227-.419-2.185C3.012 14.75 3 14.445 3 12c0-2.445.009-2.75.054-3.71s.195-1.61.419-2.185A4.392 4.392 0 0 1 4.51 4.51c.45-.458.994-.812 1.595-1.037.574-.224 1.226-.374 2.185-.419C9.25 3.012 9.555 3 12 3Z">
</path>
</svg>
</a>
</li>
</ul>
</div> -->
</div>
<Form @submit="onSubmit" :validation-schema="schema" v-slot="{ errors }" >
<Field type='text' placeholder='Name' name="name" class="w-full rounded-md py-2.5 px-4 mb-2 border text-sm outline-[#007bff]" />
<div class="bg-red-500">{{ errors.name }}</div>
<Field type='email' placeholder='Email' name="email" class="w-full rounded-md py-2.5 px-4 mb-2 border text-sm outline-[#007bff]" />
<div class="bg-red-500">{{ errors.email }}</div>
<Field type='text' placeholder='Subject' name="subject" class="w-full rounded-md py-2.5 px-4 mb-2 border text-sm outline-[#007bff]" />
<div class="bg-red-500">{{ errors.subject }}</div>
<Field type='textarea' as='textarea' placeholder='Message' rows="6" name="message" class="w-full rounded-md px-4 border text-sm pt-2.5 outline-[#007bff]" />
<div class="bg-red-500">{{ errors.message }}</div>
<button type='submit' class="text-white bg-[#007bff] hover:bg-blue-600 font-semibold rounded-md text-sm px-4 py-2.5 w-full">Send</button>
<div v-if="errors.recaptcha" class="text-red"> {{ errors.recaptcha }} </div>
<div v-if="result['success']" class="bg-green-500 w-full text-center mt-1">{{ result['success'] }}</div>
<div v-if="result['error']" class="bg-red-500 w-full text-center mt-1">{{ result['error'] }}</div>
</Form>
</div>
</div>
</GuestLayout>
</template>
<style>
@import 'vue3-easy-data-table/dist/style.css';
</style>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>

View File

@@ -0,0 +1,39 @@
<script setup>
import GuestLayout from '../Layouts/GuestLayout.vue';
</script>
<template>
<GuestLayout>
<h1 class="font-extrabold">How to use the service</h1>
<hr class="h-1 mx-auto bg-gray-100 border-0 mb-5 rounded dark:bg-gray-700">
<h2 class="font-bold mb-4">Main menu</h2>
<ol class="ml-10 list-decimal">
<li>Type in the keyword(s) to find a product for which you want to compare the price (product name, description, article number, ...)</li>
<li>You can either sort/filter the results or simply click on any table row to find the product prices</li>
<li>Price comparison will appear in the <span>"Results"</span> table</li>
</ol>
<br/>
To get the lowest price you can sort the results by price column.
Click on the country name to display the corresponding product page.
<br/>
Some countries are not included for the comparison by default (e.g. Bulgaria, Cyprus, Greece) due to technical limitations on their websites.
<br/>
You can search for a product by its article number, but without the dots. For example: 40277992
<br/>
<br/>
<img alt="Online" src="/images/pic_online.png" />
<br/>
<h2 class="font-bold mb-4">Settings menu</h2>
<p class="mb-2 underline">In the settings menu you can change:</p>
<ul class="ml-10 list-disc mb-10">
<li>the country in which to search for the product(s) to compare (default country is based on your location)</li>
<li>list of countries where the product(s) are available for comparison (default all)</li>
<li>currency re-calculation</li>
</ul>
<img alt="Settings" src="/images/pic_settings.png" />
</GuestLayout>
</template>

View File

@@ -2,15 +2,76 @@
import { getCurrentInstance, ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
import GuestLayout from '../Layouts/GuestLayout.vue';
import { useForm } from '@inertiajs/inertia-vue3'
import EasyTable from "vue3-easy-data-table";
import axios from 'axios';
const headers = ref([
{ text: "Id", value: "id", sortable: true },
{ text: "Country", value: "country", sortable: true },
{ text: "Currency", value: "currency", sortable: true },
{ text: "Rate", value: "rate", sortable: true },
]);
const currencyCountryRate = ref([]);
const rates = ref([]);
const currencyHash = ref({});
const currency = ref([]);
const countryCurrency = ref([]);
const ccountry = ref([]);
const fetch_rates = async () => {
try {
var response = await axios.get(route('rates.index'));
rates.value = response.data;
response = await axios.get(route('ccountry.active'))
ccountry.value = response.data
currency.value = rates.value.map((currency) => currency.currency);
countryCurrency.value = Object.fromEntries(
rates.value.map((currency) => [currency.country_name, currency.currency])
);
currencyHash.value = Object.fromEntries(
rates.value.map((currency) => [currency.currency, currency.rate]),
);
var i = 1;
currencyCountryRate.value = ccountry.value.map((country) => {
console.log(country);
return { "country": country.country_name, "currency": country.currency_code, "rate": currencyHash.value[country.currency_code], "id": i++ }
});
currency.value.unshift('EUR');
console.log("RATE=", rates.value);
console.log('HASH=', currencyHash.value);
console.log('CCC=',currencyCountryRate);
} catch (e) {
const response = await Swal.fire({
title: __('are you want to try again') + '?',
text: __(`${e}`),
icon: 'question',
showCancelButton: true,
showCloseButton: true,
})
response.isConfirmed && fetch()
}
}
onMounted(fetch_rates);
</script>
<template>
<GuestLayout>
<div class="flex flex-wrap gap-2 justify-center align-middle ">
<h1>Exchange</h1>
<div class="flex flex-wrap gap-2 justify-center align-middle ">
<div class="text-center grid-flow-col max-w-fit justify-items-center">
<div class="font-extrabold">Exchange rates and coutry list</div>
<div class="mt-5">
<EasyTable :rows-per-page=30 :headers="headers" :items="currencyCountryRate" alternating :hide-footer="true"></EasyTable>
</div>
</div>
</div>
</GuestLayout>
</template>
</template>
<style>
@import 'vue3-easy-data-table/dist/style.css';
</style>

View File

@@ -1,293 +1,662 @@
<script setup>
import { getCurrentInstance, ref, onMounted, onUnmounted, nextTick, computed } from 'vue';
import Swal from 'sweetalert2';
import {
getCurrentInstance,
ref,
onMounted,
onUnmounted,
computed,
nextTick,
watch,
inject,
} from "vue";
import Swal from "sweetalert2";
import Multiselect from 'vue-multiselect';
import { GChart } from 'vue-google-charts';
import Multiselect from "vue-multiselect";
import { GChart } from "vue-google-charts";
import EasyTable from "vue3-easy-data-table";
import GuestLayout from '../Layouts/GuestLayout.vue';
import GuestLayout from "../Layouts/GuestLayout.vue";
import { useForm } from '@inertiajs/inertia-vue3'
import axios from 'axios';
import IkeaLogo from './Ikea/IkeaLogo.vue';
import axios from "axios";
import filterimg from "@/assets/eglass-filter.png";
import searchimg from "@/assets/search-icon.svg";
import { settingsStore } from '../settingsStore.js';
import { FwbInput } from 'flowbite-vue';
const props = defineProps({
products: {
type: Object,
default: []
},
countryHash: {
type: Object,
default: []
},
menu: {
type: Object,
default: []
}
})
const screenWidth = ref(screen.width)
const screenHeight = ref(screen.height)
const hover = ref(true);
const hoverImage = ref(null);
const selectedRow = ref(null);
const timestamp = ref(Date.now());
// const $matomo = inject('$matomo');
console.log(props.countryHash);
let tproducts = computed(() => {
return props.products.map((prod) => {
prod.salesPrice = parseFloat(prod.salesPrice);
prod.country = props.countryHash[prod.country];
return prod;
})
})
const type = 'GeoChart';
const form = useForm({
countries: '',
country: '',
codes: '',
currency: 'EUR',
});
const ccodes = ref([]);
const ccountry = ref([]);
const ccountry_filter = ref([['Country'],]);
const ccountry_list = ref(['TEST']);
const selected = ref(null);
const article = ref('');
const currency = ref([]);
const hrates = ref([
{ text: "Currency", value: "currency", sortable: true },
{ text: "Country", value: "country_name", sortable: true },
{ text: "Rate", value: "rate", sortable: true }
]);
// { "country": "AT", "code": "50161321", "url": "https://www.ikea.com/at/de/p/hol-aufbewahrungstisch-akazie-50161321/", "name": "HOL", "typeName": "Aufbewahrungstisch", "mainImageUrl": "https://www.ikea.com/at/de/images/products/hol-aufbewahrungstisch-akazie__0104310_pe251255_s5.jpg", "itemNoGlobal": "50161321", "salesPrice": "80.99", "tag": "FAMILY_PRICE", "last_mod": "2023-12-03 16:44:24" },
const hproducts = ref([
{ text: "Country", value: "country", sortable: true },
{ text: "Name", value: "name", sortable: true },
{ text: "Price", value: "salesPrice", sortable: true }
const sdropdown = ref([
{ text: "Description", value: "typeName" },
{ text: "Item name", value: "name" },
{ text: "Code of product", value: "code" },
]);
const products = ref([]);
const searchField = ref('descLong');
const searchValue = ref('');
const countryHash = ref([]);
const countryCurrency = ref({});
const showItemFilter = ref(false);
const showDescFilter = ref(false);
const showDescLongFilter = ref(false);
const showUnitsFilter = ref(false);
const geoip = ref({});
const rates = ref([]);
const options_items = ref([]);
const selected_item = ref(null);
const itemCode = ref(null);
const googlemapbox = ref(null);
const googlemapchart = ref(null);
const gChartWidth = computed(() => screenWidth.value > 420 ? 400 : (screenWidth.value - 30 - 5));
const gChartHeight = computed(() => gChartWidth.value == 400 ? 250 : (screenWidth.value *67/100) - 28);
console.log('GCH',screenWidth.value, screenHeight.value, gChartWidth.value,gChartHeight.value);
const options = {
region: 150,
width: 700,
height: 500,
colorAxis: {colors: ['#0000ff']},
backgroundColor: '#81d4fa',
datalessRegionColor: '#ffffff',
//datalessRegionColor: '#f8bbd0',
//defaultColor: '#f5f5f5',
width: gChartWidth.value,
height: gChartHeight.value,
};
const chart_settings = {
packages: ['geochart', 'map'],
packages: ["geochart", "map"],
mapsApiKey: "AIzaSyAJaLArHgTmQPMOSogitG-umhZilVIgdNU",
};
const gchartEvents = ref({
regionClick: () => {
const selection = getSelection()
const selection = getSelection();
console.log(selection);
console.log("T");
},
});
const onHover = (data) => {
console.log('HOVER',data.target.src);
hoverImage.value = data.target.src;
hover.value = !hover.value;
};
const chart_coutries = computed(() => {
let countries = [["Country", "Count"]];
for (let i=0; i < settingsStore.ccountry_list.length ; i++) {
if (settingsStore.ccountry_list[i].offline == (settingsStore.online ? 'N' : 'Y') || settingsStore.online == true)
countries.push([settingsStore.ccountry_list[i].name, settingsStore.products_count[settingsStore.ccountry_list[i].code] ]);
}
return countries;
})
const tproducts = computed(() => {
console.log("PR=", products);
console.log("CC=", countryCurrency.value);
return products.value.map((p) => {
const prod = { ...p };
prod.currency = countryCurrency.value[prod.countryName];
if (currencyHash.value[prod.currency])
prod.calcPrice = prod.salesPrice / currencyHash.value[prod.currency];
else {
prod.calcPrice = prod.salesPrice;
prod.currency = "EUR";
}
if (prod.tag == "NONE") prod.tag = "";
prod.salesPrice = Math.round(prod.salesPrice * 100) / 100;
prod.calcPrice =
Math.round(prod.calcPrice * settingsStore.currencyCoef * 100) / 100;
console.log('CHA=',currencyHash.value[prod.currency]);
prod.rate =
Math.round((currencyHash.value[prod.currency] == undefined? 1.0 : currencyHash.value[prod.currency] ) / (settingsStore.currency != "EUR" ? currencyHash.value[settingsStore.currency] : 1.0)*100)/100;
if (prod.tag != null && prod.tag.length > 1) prod.tag = prod.tag.replace(/_/g, " ");
return prod;
});
});
const bodyRowClassNameFunction = (item) => {
if (item.country == settingsStore.country.code) return "result-country";
};
const filterOptions = computed(() => {
const filterOptionsArray = [];
if (productsCriteria.value != "All" && productsCriteria.value != null) {
filterOptionsArray.push({
field: "item",
comparison: "=",
criteria: productsCriteria.value,
});
}
},);
if (descCriteria.value != "All" && descCriteria.value != null) {
filterOptionsArray.push({
field: "desc",
comparison: "=",
criteria: descCriteria.value,
});
}
if (unitsCriteria.value != "All" && unitsCriteria.value != null) {
filterOptionsArray.push({
field: "units",
comparison: "=",
criteria: unitsCriteria.value,
});
}
return filterOptionsArray;
});
let uniqProducts = computed(() => {
var output = ["All"];
var keys = [];
options_items.value.forEach((element) => {
var key = element.item;
if (keys.indexOf(key) === -1) {
keys.push(key);
output.push(element.item);
}
});
console.log("OUT", output);
return output;
});
let uniqDesc = computed(() => {
var output = ["All"];
var keys = [];
options_items.value.forEach((element) => {
var key = element.desc;
if (keys.indexOf(key) === -1) {
keys.push(key);
output.push(element.desc);
}
});
console.log("OUT2", output);
return output;
});
let uniqUnits = computed(() => {
var output = ["All"];
var keys = [];
options_items.value.forEach((element) => {
var key = element.units;
if (keys.indexOf(key) === -1) {
keys.push(key);
output.push(element.units);
}
});
console.log("OUT3", output);
return output;
});
const productsCriteria = ref(uniqProducts.value[0]);
const descCriteria = ref(uniqDesc.value[0]);
const unitsCriteria = ref(uniqUnits.value[0]);
const type = "GeoChart";
settingsStore.field = sdropdown.value[0];
const ccodes = ref([]);
const ccountry = ref([]);
const ccountry_list = ref(["TEST"]);
const currencyHash = ref({});
const currency = ref([]);
const items = ref([]);
const hrates = ref([
{ text: "Currency", value: "currency", sortable: true },
{ text: "Country", value: "country_name", sortable: true },
{ text: "Rate", value: "rate", sortable: true },
]);
// { "country": "AT", "code": "50161321", "url": "https://www.ikea.com/at/de/p/hol-aufbewahrungstisch-akazie-50161321/", "name": "HOL", "typeName": "Aufbewahrungstisch", "mainImageUrl": "https://www.ikea.com/at/de/images/products/hol-aufbewahrungstisch-akazie__0104310_pe251255_s5.jpg", "itemNoGlobal": "50161321", "salesPrice": "80.99", "tag": "FAMILY_PRICE", "last_mod": "2023-12-03 16:44:24" },
const tprice = computed(() => {
return `Price / ${settingsStore.currency}`;
})
const hproducts = ref([
{ text: "Country", value: "countryName", sortable: true },
{ text: "Tag", value: "tag", sortable: true },
{ text: "Local Price", value: "salesPrice", sortable: true },
{ text: "Cur", value: "currency", sortable: false },
{ text: "Rate", value: "rate" },
{ text: tprice, value: "calcPrice", sortable: true },
]);
const hresults = ref([
{ text: "Code", value: "code", sortable: true },
// { text: "Name of product", value: "item", sortable: true },
// { text: "Description", value: "desc", sortable: true },
{ text: "Description Long", value: "descLong", sortable: true },
{ text: "Units", value: "units", sortable: true },
{ text: "Price", value: "price", sortable: true },
{ text: "Image", value: "img", sortable: false },
]);
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms))
}
return new Promise((resolve) => setTimeout(resolve, ms));
};
const fetch = async () => {
try {
const response = await axios.get(route('ccountry.active'))
ccountry.value = response.data
const response = await axios.get(route("ccountry.active"));
ccountry.value = response.data;
let aCntry = ccountry.value.map((country) => [country.country_name]);
ccountry_filter.value.push(...aCntry);
settingsStore.ccountry_filter.push(...aCntry);
ccountry_list.value = ccountry.value.map((country) => country.country_name);
var i = 1;
items.value = ccountry.value.map((country) => { return { "country": country.country_name, "currency": country.currency_code, "id": i++ } });
console.log("TEST=", ccountry_filter.value, ccountry_list.value);
items.value = ccountry.value.map((country) => {
return {
country: country.country_name,
currency: country.currency_code,
id: i++,
};
});
console.log("TEST=", settingsStore.ccountry_filter, ccountry_list.value);
const response2 = await axios.get(route("settings.index"));
settingsStore.settings = response2.data;
console.log("SETTINGS=",settingsStore.settings);
const response3 = await axios.get(route("products.count"));
settingsStore.products_count = Object.fromEntries(
response3.data.map((c) => [c.country, c.cnt])
);
console.log("PC=",settingsStore.products_count);
} catch (e) {
const response = await Swal.fire({
title: __('are you want to try again') + '?',
title: __("are you want to try again") + "?",
text: __(`${e}`),
icon: 'question',
icon: "question",
showCancelButton: true,
showCloseButton: true,
})
});
response.isConfirmed && fetch()
response.isConfirmed && fetch();
}
}
};
const fetch_rates = async () => {
try {
const response = await axios.get(route('rates.index'))
const response = await axios.get(route("rates.index"));
rates.value = response.data;
currency.value = rates.value.map((currency) => currency.currency);
currency.value.unshift('EUR');
countryCurrency.value = Object.fromEntries(
rates.value.map((currency) => [currency.country_name, currency.currency])
);
currencyHash.value = Object.fromEntries(
rates.value.map((currency) => [currency.currency, currency.rate])
);
currency.value.unshift("EUR");
console.log("RATE=", rates.value);
console.log("HASH=", currencyHash.value);
} catch (e) {
const response = await Swal.fire({
title: __('are you want to try again') + '?',
title: __("are you want to try again") + "?",
text: __(`${e}`),
icon: 'question',
icon: "question",
showCancelButton: true,
showCloseButton: true,
})
});
response.isConfirmed && fetch()
response.isConfirmed && fetch();
}
}
};
const fetch_ccodes = async () => {
try {
const response = await axios.get(route('ccountry.codes'))
const response2 = await axios.get(route("geo.ip.get"));
geoip.value = response2.data;
const response = await axios.get(route("ccountry.codes"));
ccodes.value = response.data;
ccodes.value = Object.entries(ccodes.value).map(([k, v]) => { if (v !== null) return { "country": k, "code": v } }).filter(n => n);
ccodes.value = Object.entries(ccodes.value)
.map(([k, v]) => {
if (v !== null) return { country: k, code: v };
})
.filter((n) => n);
console.log("ccodes=", ccodes.value);
} catch (e) {
const response = await Swal.fire({
title: __('are you want to try again') + '?',
title: __("are you want to try again") + "?",
text: __(`${e}`),
icon: 'question',
icon: "question",
showCancelButton: true,
showCloseButton: true,
})
});
response.isConfirmed && fetch()
response.isConfirmed && fetch();
}
}
};
const async_search = async () => {
if (settingsStore.text.length < 2) return;
timestamp.value = Date.now();
const async_search = async (item) => {
try {
if (item.length >= 2) {
let country = null;
if (settingsStore.country.code !== undefined) country = settingsStore.country.code;
console.log('SEARCH', item, form.country.code, route('products.search', [item, form.country.code]));
const response = await axios.get(route('products.search', [item, form.country.code]));
console.log('SETTINGS',settingsStore.country);
console.log('URL',route("products.search", ['all', settingsStore.text, country]));
const response = await axios.get(route("products.search", ['all', settingsStore.text, country]));
options_items.value = response.data.map((i) => { return { "item": i.name, "desc": i.typeName, "img": i.mainImageUrl, "code": i.code, "url": i.url } });
console.log("VALUES=", options_items.value);
}
options_items.value = response.data.map((i) => {
return {
item: i.name,
desc: i.typeName,
img: i.mainImageUrl,
code: i.code,
url: i.url,
price: parseFloat(i.salesPrice),
units: i.priceUnit,
descLong: i.mainImageAlt,
globalCode: i.itemNoGlobal,
};
});
console.log("VALUES=", options_items.value);
productsCriteria.value = null;
} catch (e) {
const response = await Swal.fire({
title: __('are you want to try again') + '?',
title: __("are you want to try again") + "?",
text: __(`${e}`),
icon: 'question',
icon: "question",
showCancelButton: true,
showCloseButton: true,
})
});
response.isConfirmed && fetch()
response.isConfirmed && fetch();
}
}
};
function customLabel({ item, desc, code }) {
//console.log(item);
return `${item} - ${desc} - ${code}`;
}
const headers = ref([
{ text: "Id", value: "id", sortable: true },
{ text: "Country", value: "country", sortable: true },
{ text: "Currency", value: "currency" },
]);
const items = ref([]);
function selectCountry(item, id) {
let cCntry = [['Country'],];
console.log(form.countries);
let aCntry = form.countries.map((country) => [country]);
ccountry_filter.value = cCntry;
ccountry_filter.value.push(...aCntry);
const clear_all = () => {
settingsStore.text = '';
products.value = [];
options_items.value = [];
}
onMounted(fetch);
onMounted(fetch_rates);
onMounted(fetch_ccodes);
onMounted(() => {
console.log('HEIGHT MAP', googlemapbox.value);
});
const showRow = async (item) => {
console.log("ITEM=", item);
itemCode.value = item.code;
try {
const response = await axios.post(route("products.compare"), {
codes: item.globalCode,
countries: settingsStore.countries,
currency: settingsStore.currency,
online: settingsStore.online,
text: settingsStore.text,
country: settingsStore.country.code,
});
//console.log('MATOMO',$matomo);
products.value = response.data.products;
countryHash.value = response.data.countryHash;
console.log("TEST=", response.data);
nextTick(() => {
options.width=googlemapbox.value.clientWidth;
googlemapchart.value += 1;
})
} catch (e) {
const response = await Swal.fire({
title: __("are you want to try again") + "?",
text: __(`${e}`),
icon: "question",
showCancelButton: true,
showCloseButton: true,
});
response.isConfirmed && fetch();
}
};
const submit = () => {
console.log('ITEM=', form);
if (form.codes.length == 0) {
console.log("ITEM=", form);
if (settingsStore.codes.length == 0) {
Swal.fire({
title: "Empty code",
text: "You must enter product!",
icon: "error"
icon: "error",
});
return false;
}
form.post(route('products.compare'));
settingsStore.post(route("products.compare"));
};
const selectedRowClassNameFunction = (item) => {
if (item.code == itemCode.value) return 'selected-row';
};
</script>
<template>
<GuestLayout>
<div class="flex flex-wrap gap-2 justify-center align-middle ">
<div class="justify-center rounded-md border-black border-8 max-h-[518px]">
<GChart :events="gchartEvents" :type="type" :data="ccountry_filter" :options="options"
:settings="chart_settings" />
</div>
<div class="mr-auto">
<form @submit.prevent="submit">
<div class="flex flex-col start-0">
<span class="font-extrabold font-mono">Zadaj krajiny v ktorych chces vyhadavat</span>
</div>
<div>
<multiselect :close-on-select="false" :multiple="true" v-model="form.countries" :options="ccountry_list"
@select="selectCountry" @remove="selectCountry">
</multiselect>
</div>
<div class="flex flex-col start-0">
<span class="font-extrabold font-mono">Krajina produktu na vyhladanie</span>
</div>
<div>
<multiselect :close-on-select="false" :multiple="false" v-model="form.country" :options="ccodes"
label="country" track-by="code">
</multiselect>
</div>
<div>
<span class="font-extrabold font-mono">Zadaj polozku</span>
</div>
<div>
<multiselect v-model="form.codes" label="item" track-by="item" :custom-label="customLabel"
:internal-search="false" placeholder="Find item" :searchable="true" :options="options_items"
@search-change="async_search" />
</div>
<div>
<span class="font-extrabold font-mono">Prepocet do meny</span>
</div>
<div>
<multiselect v-model="form.currency" :multiple="false" :allow-empty="false" placeholder="Currency"
:searchable="true" :options="currency" />
</div>
<div class="text-end mt-2">
<button type="submit"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm w-full sm:w-auto px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
:disabled="form.processing" :class="{ 'opacity-25': form.processing }">
Search
</button>
</div>
</form>
<div class="mt-5">
<EasyTable :rows-per-page=10 :headers="headers" :items="items" alternating></EasyTable>
</div>
<div class="mt-5">
<EasyTable :rows-per-page=15 :headers="hrates" :items="rates" alternating></EasyTable>
</div>
</div>
<div>
<div class="flex flex-col start-0">
<span class="font-extrabold font-mono">Vysledky vyhladavania</span>
</div>
<div>
<EasyTable :rows-per-page=15 :headers="hproducts" :items="tproducts" alternating>
<template #item-name="{ name, url }">
<a class="underline" target="_blank" :href="url">{{ name }}</a>
</template>
</EasyTable>
</div>
</div>
</div>
<GuestLayout>
</GuestLayout>
<div class="flex flex-wrap gap-2 justify-center align-middle">
<div id="popup-modal" tabindex="-1" :class="'hidden'"
class="absolute inset-y-1/3 left-1/3 sm:inset-10 sm:left-10 z-50 justify-center items-center w-full max-h-full">
<div class="relative p-4 w-full max-w-md max-h-full">
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<button @click="hover=true" type="button"
class="absolute top-3 end-2.5 text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
data-modal-hide="popup-modal">
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 14 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" />
</svg>
<span class="sr-only">Close modal</span>
</button>
<div class="p-4 md:p-5 text-center">
<img :src="hoverImage" />
<!-- <button data-modal-hide="popup-modal" type="button" class="text-white bg-red-600 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 dark:focus:ring-red-800 font-medium rounded-lg text-sm inline-flex items-center px-5 py-2.5 text-center">
Yes, I'm sure
</button> -->
<button @click="hover=true" data-modal-hide="popup-modal" type="button"
class="py-2.5 px-5 mt-2 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700">Close</button>
</div>
</div>
</div>
</div>
<div class="flex flex-col start-0 row-start-1">
<div ref="googlemapbox" class="justify-center rounded-md border-black border-8 max-h-[328px]">
<GChart :key="googlemapchart" :events="gchartEvents" :type="type" :data="chart_coutries"
:options="options" :settings="chart_settings" :resizeDebounce="500" />
</div>
<div class="flex flex-col start-0">
<!-- <span class="font-extrabold font-mono">Results <b v-if="itemCode">for {{ itemCode }}</b> -->
<span class="font-extrabold font-mono">Prices across Europe
</span>
</div>
<div>
<EasyTable id="results" table-class-name="results" sortBy="countryName" :rows-per-page="30"
:headers="hproducts" :items="tproducts" :body-row-class-name="bodyRowClassNameFunction"
:hide-rows-per-page="true" :hide-footer="true"
sort-by="calcPrice" alternating>
<template #item-countryName="{ countryName, url }">
<a class="underline" target="_blank" :href="url">{{
countryName
}}</a>
</template>
</EasyTable>
<!--
<div v-if="settingsStore.online == true">Online</div>
<div v-else-if="'LAST_REFRESH_TIME' in settingsStore.settings">Last Refresh Time: {{
settingsStore.settings["LAST_REFRESH_TIME"] }}</div> -->
</div>
</div>
<div class="md:w-2/5">
<form @submit.prevent="submit">
<div>
<div class="flex flex-col start-0">
<div class="w-full">
<div>
<span class="font-extrabold font-mono">Search IKEA product:</span>
</div>
<div>
<fwb-input @input="async_search" v-model="settingsStore.text" :placeholder="settingsStore.online ? 'Code of product' : 'METOD, BESTA, MALM, ...'"
size="md">
<template #suffix>
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400"
fill="none" stroke="currentColor" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"
stroke-linecap="round" stroke-linejoin="round" stroke-width="2" />
</svg>
</template>
</fwb-input>
</div>
</div>
<div>
<!--
<div>
<span class="font-extrabold font-mono">Online:</span>
</div>
<div>
<input type="checkbox"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
v-model="settingsStore.online" @input="clear_all" />
</div>
-->
</div>
</div>
</div>
<div class="font-extrabold font-mono mt-2">Click on any table row to find the product prices</div>
</form>
<div class="mt-3">
<input v-if="showDescLongFilter" placeholder="Search in long description"
class="mb-1 px-2 py-1 placeholder-blueGray-300 text-blueGray-600 relative bg-white bg-white rounded text-sm border border-blueGray-300 outline-none focus:outline-none focus:ring w-full"
type="text" v-model="searchValue">
<EasyTable class="none" :rows-per-page="20" :headers="hresults" :items="options_items"
:search-field="searchField" :search-value="searchValue"
:body-row-class-name="selectedRowClassNameFunction" @click-row="showRow"
:filter-options="filterOptions"
alternating>
<template #item-img="{ code, img }">
<img v-on:mouseover="onHover" class="h-12" :src="img" :alt="code" />
</template>
<template #header-item="header">
<div class="filter-column">
<img :src="filterimg" class="filter-icon" alt="filter"
@click.stop="showItemFilter = !showItemFilter" />
{{ header.text }}
<div class="filter-menu filter-sport-menu" v-if="showItemFilter">
<multiselect v-model="productsCriteria" :options="uniqProducts"></multiselect>
</div>
</div>
</template>
<template #header-descLong="header">
<div class="filter-column">
<img :src="searchimg" class="filter-icon" alt="filter long description"
@click.stop="showDescLongFilter = !showDescLongFilter" />
{{ header.text }}
</div>
</template>
<template #header-desc="header">
<div class="filter-column">
<img :src="filterimg" class="filter-icon" alt="filter description"
@click.stop="showDescFilter = !showDescFilter" />
{{ header.text }}
<div class="filter-menu filter-sport-menu" v-if="showDescFilter">
<multiselect v-model="descCriteria" :options="uniqDesc"></multiselect>
</div>
</div>
</template>
<template #header-units="header">
<div class="filter-column">
<img :src="filterimg" class="filter-icon" alt="filter units"
@click.stop="showUnitsFilter = !showUnitsFilter" />
{{ header.text }}
<div class="filter-menu filter-menu-units" v-if="showUnitsFilter">
<multiselect v-model="unitsCriteria" :options="uniqUnits"></multiselect>
</div>
</div>
</template>
</EasyTable>
</div>
</div>
<div></div>
</div>
</GuestLayout>
</template>
<style>
@import 'vue3-easy-data-table/dist/style.css';
@import "vue3-easy-data-table/dist/style.css";
.filter-column {
display: flex;
align-items: center;
justify-items: center;
position: relative;
}
.filter-icon {
cursor: pointer;
display: inline-block;
width: 15px !important;
height: 15px !important;
margin-right: 4px;
}
.filter-menu {
padding: 5px 5px;
z-index: 1;
position: absolute;
top: 40px;
width: 328px;
background-color: #fff;
border: 1px solid #e0e0e0;
}
.filter-menu-units {
padding: 5px 5px;
z-index: 1;
position: absolute;
top: 40px;
width: 128px;
}
.selected-row {
--easy-table-body-row-hover-background-color: #e5e6f9;
--easy-table-body-row-background-color: #e5e6f9;
--easy-table-body-row-font-color: #000;
--easy-table-body-even-row-background-color: #e5e6f9;
}
:root .result-country {
--easy-table-body-row-background-color: #e5e6f9;
--easy-table-body-even-row-background-color: #e5e6f9;
}
</style>
<style src="vue-multiselect/dist/vue-multiselect.css"></style>

View File

@@ -0,0 +1,127 @@
<script setup>
import DashboardLayout from '@/Layouts/DashboardLayout.vue';
import Card from '@/Components/Card.vue';
import Icon from '@/Components/Icon.vue';
import Builder from '@/Components/DataTable/Builder.vue';
import Th from '@/Components/DataTable/Th.vue';
import VueJsonPretty from "vue-json-pretty";
import {ref} from 'vue';
const dateValue = ref([]);
</script>
<template>
<DashboardLayout
title="Logging Activity"
>
<Card class="bg-white dark:bg-gray-700 dark:text-gray-200">
<template #header>
<div class="flex items-center space-x-2 bg-gray-200 dark:bg-gray-800 p-2">
<p class="lowercase first-letter:capitalize font-semibold">
Logging for Today
</p>
</div>
</template>
<template #body>
<Builder
:url="route('superuser.activity.logging')"
>
<template #thead="table">
<tr class="bg-gray-200 dark:bg-gray-800 border-gray-300 dark:border-gray-900">
<Th
:table="table"
:sort="false"
name="id"
class="border p-2 text-center"
>
ID
</Th>
<Th
:table="table"
:sort="true"
name="level_name"
class="border px-3 py-2 text-center whitespace-nowrap"
>
Level Name
</Th>
<Th
:table="table"
:sort="true"
name="message"
class="border px-3 py-2 text-center whitespace-nowrap"
>
Message
</Th>
<Th
:table="table"
:sort="true"
name="context"
class="border px-3 py-2 text-center whitespace-nowrap"
>
Context
</Th>
<Th
:table="table"
:sort="true"
name="logged_at"
class="border px-3 py-2 text-center whitespace-nowrap"
>
Logged At
</Th>
</tr>
</template>
<template #tbody="{ data }">
<TransitionGroup
enterActiveClass="transition-all duration-100"
leaveActiveClass="transition-all duration-50"
enterFromClass="opacity-0 -scale-y-100"
leaveToClass="opacity-0 -scale-y-100"
>
<tr
v-for="(rec, i) in data"
:key="i"
class="dark:hover:bg-gray-600 dark:border-gray-800 transition-all duration-300"
>
<td class="px-2 py-1 border border-inherit text-center">
{{ rec.id }}
</td>
<td class="px-2 py-1 border border-inherit ">
{{ rec.level_name }}
</td>
<td class="px-2 py-1 border border-inherit ">
{{ rec.message }}
</td>
<td class="px-2 py-1 border border-inherit ">
<vue-json-pretty :data="JSON.parse(rec.context)" />
</td>
<td class="px-2 py-1 border border-inherit ">
{{ rec.logged_at }}
</td>
</tr>
</TransitionGroup>
</template>
</Builder>
</template>
</Card>
</DashboardLayout>
</template>
<style>
@import 'vue-json-pretty/lib/styles.css';
</style>

View File

@@ -11,6 +11,7 @@ import Swal from 'sweetalert2';
import { Inertia } from '@inertiajs/inertia';
import axios from 'axios';
import * as commons from './common.js'
// import VueMatomo from 'vue-matomo';
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
@@ -25,18 +26,25 @@ Object.keys(commons).forEach(key => Object.defineProperty(window, key, {
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue'),import.meta.glob('./assets/**') ),
setup({ el, app, props, plugin }) {
return createApp({ render: () => h(app, props) })
.use(plugin)
.use(ZiggyVue, Ziggy)
// .use(VueMatomo, {
// // Configure your matomo server and site by providing
// host: 'https://matomo.soson.eu',
// siteId: 3,
// })
.mixin({
methods: {
...commons,
themes: () => Themes,
},
})
.mount(el);
// .provide('$matomo', VueMatomo)
.mount(el)
},
});
@@ -60,7 +68,7 @@ Inertia.on('finish', commons.authorization)
Inertia.on('finish', () => {
const { $flash } = usePage().props.value
const { success, error, info, warning } = $flash
if (success) {
Toast.fire({
text: success,
@@ -68,14 +76,14 @@ Inertia.on('finish', () => {
icon: 'success',
})
}
if (error) {
Toast.fire({
text: error,
icon: 'error',
})
}
if (info) {
Toast.fire({
text: info,
@@ -83,7 +91,7 @@ Inertia.on('finish', () => {
icon: 'info',
})
}
if (warning) {
Toast.fire({
text: warning,
@@ -91,4 +99,4 @@ Inertia.on('finish', () => {
icon: 'warning',
})
}
})
})

BIN
resources/js/assets/.DS_Store vendored Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 B

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 511.846 511.846" xml:space="preserve">
<style>
path {
fill: black;
}
@media (prefers-color-scheme: dark) {
path { fill: white; }
}
</style>
<g>
<g>
<g>
<path d="M443.068,312.107c-4.079-2.389-9.301-1.024-11.674,3.046l-97.818,166.835c-7.066,12.228-22.741,16.444-34.594,9.6
l-19.055-12.348c-3.959-2.569-9.242-1.442-11.802,2.517c-2.569,3.951-1.434,9.242,2.517,11.802l19.43,12.578
c6.699,3.874,14.029,5.709,21.265,5.709c14.754,0,29.107-7.637,36.983-21.274l97.783-166.793
C448.486,319.718,447.13,314.487,443.068,312.107z"/>
<path d="M468.881,0H332.348c-26.402,0-46.071,8.405-65.766,28.1c-3.336,3.337-3.336,8.73,0,12.066
c3.337,3.337,8.73,3.337,12.066,0c16.623-16.623,31.684-23.1,53.7-23.1h136.533c14.114,0,25.6,11.486,25.6,25.6V179.2
c0,22.025-6.477,37.077-23.108,53.7L217.95,486.682c-9.677,9.66-26.539,9.668-36.198,0L24.866,329.796
c-4.838-4.838-7.501-11.264-7.501-18.108c0-6.835,2.662-13.261,7.492-18.091L253.909,64.87c3.524,0.896,7.057,1.792,10.718,2.773
c34.714,9.301,62.106,18.91,82.526,27.486c-4.019,7.441-6.272,15.778-6.272,24.337c0,28.237,22.963,51.2,51.2,51.2
c28.237,0,51.2-22.963,51.2-51.2c0-28.237-22.963-51.2-51.2-51.2c-4.719,0-8.533,3.823-8.533,8.533s3.814,8.533,8.533,8.533
c18.825,0,34.133,15.309,34.133,34.133c0,18.825-15.309,34.133-34.133,34.133s-34.133-15.309-34.133-34.133
c0-6.144,1.681-12.143,4.779-17.365c14.78,7.074,24.021,13.039,27.674,16.845c-1.562,0.444-3.977,0.939-7.62,1.254
c-4.693,0.418-8.166,4.557-7.748,9.25c0.393,4.437,4.113,7.791,8.491,7.791c0.247,0,0.503-0.017,0.759-0.034
c5.248-0.469,21.214-1.877,24.286-13.321c8.405-31.394-114.756-66.091-139.529-72.73
c-37.897-10.155-74.291-17.101-102.494-19.558c-34.654-3.029-51.533,0.913-54.707,12.774
c-1.519,5.692-5.572,20.804,63.616,48.085c4.378,1.732,9.344-0.418,11.068-4.804c1.724-4.386-0.427-9.344-4.813-11.068
c-33.425-13.184-47.275-22.844-51.712-27.392c10.522-3.089,46.507-2.193,104.457,10.982L12.8,281.523
c-8.064,8.064-12.501,18.773-12.501,30.165c0,11.401,4.437,22.118,12.501,30.174l156.885,156.885
c8.047,8.055,18.765,12.493,30.157,12.493c11.401,0,22.11-4.437,30.174-12.501l253.431-253.773
c19.703-19.695,28.1-39.364,28.1-65.766V42.667C511.548,19.14,492.408,0,468.881,0z"/>
<path d="M279.398,224.367c-3.336-3.337-8.73-3.337-12.066,0l-4.608,4.608c-6.75-4.471-14.805-7.108-23.492-7.108
c-23.526,0-42.667,19.14-42.667,42.667c0,12.715,5.248,22.767,9.822,30.114c4.267,6.852,7.245,16.614,7.245,21.086
c0,14.114-11.486,25.6-25.6,25.6s-25.6-11.486-25.6-25.6c0-5.18,1.544-10.172,4.463-14.438c2.662-3.883,1.664-9.199-2.219-11.861
c-3.891-2.654-9.199-1.664-11.861,2.219c-4.873,7.117-7.45,15.445-7.45,24.081c0,8.687,2.637,16.742,7.108,23.492l-4.608,4.608
c-3.337,3.336-3.337,8.73,0,12.066c1.664,1.664,3.849,2.5,6.033,2.5c2.185,0,4.369-0.836,6.033-2.5l4.608-4.608
c6.75,4.471,14.805,7.108,23.492,7.108c23.526,0,42.667-19.14,42.667-42.667c0-8.311-4.13-20.975-9.822-30.114
c-5.077-8.149-7.245-14.455-7.245-21.086c0-14.114,11.486-25.6,25.6-25.6c6.844,0,13.03,2.731,17.621,7.125
c0.145,0.154,0.196,0.358,0.341,0.512c0.154,0.145,0.358,0.196,0.512,0.341c4.395,4.591,7.125,10.778,7.125,17.621
s-2.671,13.286-7.518,18.133c-3.337,3.328-3.337,8.73-0.009,12.066c3.319,3.337,8.73,3.328,12.066,0.009
c8.073-8.073,12.527-18.799,12.527-30.208c0-8.687-2.637-16.742-7.108-23.492l4.608-4.608
C282.735,233.097,282.735,227.703,279.398,224.367z"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="100" height="100" viewBox="0 0 50 50">
<path d="M 21 3 C 11.621094 3 4 10.621094 4 20 C 4 29.378906 11.621094 37 21 37 C 24.710938 37 28.140625 35.804688 30.9375 33.78125 L 44.09375 46.90625 L 46.90625 44.09375 L 33.90625 31.0625 C 36.460938 28.085938 38 24.222656 38 20 C 38 10.621094 30.378906 3 21 3 Z M 21 5 C 29.296875 5 36 11.703125 36 20 C 36 28.296875 29.296875 35 21 35 C 12.703125 35 6 28.296875 6 20 C 6 11.703125 12.703125 5 21 5 Z"></path>
</svg>

After

Width:  |  Height:  |  Size: 523 B

View File

@@ -0,0 +1,17 @@
import { reactive } from 'vue';
export const settingsStore = reactive({
countries: "",
country: "",
field: "",
online: false,
text: "",
currency: "EUR",
online: false,
ccountry_filter: [["Country"]],
products_count: {},
ccountry_list: [],
currencyCoef: 1.0,
settings: {},
});

View File

@@ -4,7 +4,8 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Ikea price comparison for Europe">
<meta name="keywords" content="ikea, price, comparison, compare, check, scanner, product, ikea price, ikea comparison, price comparison, ikea compare, price compare, vergleichen, preis, preise, ikea preis, ikea preise, ikea vergleichen, ikea vergleichen preis, ikea vergleichen preise, vergleichen preis, vergleichen preise, valeur, products">
<title inertia>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
@@ -12,19 +13,37 @@
<!-- Icons -->
<link rel="stylesheet" href="{{ url('/vendors/fontawesome/css/all.min.css') }}">
<link rel="shortcut icon" href="{{ asset('images/favicon.png') }}">
<!-- Scripts -->
<style>
.gradient {
background: linear-gradient(90deg, #d53369 0%, #daae51 100%);
}
</style>
@routes
@vite('resources/js/app.js')
@inertiaHead
<!-- Matomo -->
<script>
var _paq = window._paq = window._paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="https://matomo.soson.eu/";
_paq.push(['setTrackerUrl', u+'matomo.php']);
_paq.push(['setSiteId', '3']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src=u+'matomo.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Code -->
</head>
<body class="font-sans antialiased bg-gray-100">
<body class="font-sans antialiased bg-gray-100 relative min-h-screen">
@inertia
</body>

View File

@@ -11,7 +11,7 @@
html, body {
background-color: #fff;
color: #636b6f;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; */
font-weight: 100;
height: 100vh;
margin: 0;

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Feedback Mail From IKEA Price Crawler</title>
</head>
<body>
<h3>User Detail:</h3>
<h4>Name: {{ $data['name'] }}</h4>
<h4>Email: {{ $data['email'] }}</h4>
<h4>Subject: {{ $data['subject'] }}</h4>
<hr/>
<p>{{ $data['message'] }}</p>
</body>
</html>

View File

@@ -5,9 +5,14 @@ use Inertia\Inertia;
use App\Http\Controllers\CountryCodeController;
use App\Http\Controllers\CountryCompareController;
use App\Http\Controllers\CurrencyRatesController;
use App\Http\Controllers\GeoIPController;
use App\Http\Controllers\IkeaProductsController;
use App\Http\Controllers\ProductsCompareController;
use App\Http\Controllers\FeedbackController;
use App\Http\Controllers\SettingsController;
use App\Http\Controllers\OnlineCompareController;
use App\Http\Controllers\ProductsCountController;
use App\Http\Controllers\LoggingController;
/*
|--------------------------------------------------------------------------
@@ -22,28 +27,38 @@ use App\Http\Controllers\ProductsCompareController;
Route::get('/', function () {
return Inertia::render('IkeaRoot');
})->name('root');
})->name('root');
Route::get('/about/', function () {
return Inertia::render('IkeaAbout');
})->name('about');
return Inertia::render('IkeaAbout');
})->name('about');
Route::get('/exchange/', function () {
return Inertia::render('IkeaExchange');
})->name('exchange');
return Inertia::render('IkeaExchange');
})->name('exchange');
Route::get('/doc/', function () {
return Inertia::render('IkeaDoc');
})->name('doc');
Route::post('users-send-email', [FeedbackController::class, 'sendEmail'])->name('ajax.send.email');
//Route::get('/products/online', [OnlineCompareController::class,'compare'])->name('products.online.compare');
Route::get('/products/count', [ProductsCountController::class,'index'])->name('products.count');
Route::get('/menu/get', [App\Http\Controllers\Superuser\UserMenuController::class, 'get'])->name('menu.user');
Route::get('/ip/get/{ip?}', [GeoIPController::class, 'index'])->name('geo.ip.get');
Route::get('/ccountry/', [CountryCodeController::class, 'index'])->name('ccountry.index');
Route::get('/settings/', [SettingsController::class, 'index'])->name('settings.index');
Route::get('/ccountry/codes/', [CountryCodeController::class, 'codes'])->name('ccountry.codes');
Route::get('/ccountry/active/', [CountryCodeController::class, 'active'])->name('ccountry.active');
Route::get('/search/{id}',[CountryCompareController::class,'search'])->name('ccompare.search');
Route::get('/rates/', [CurrencyRatesController::class, 'index'])->name('rates.index');
Route::get('/products/{item}/{country?}', [IkeaProductsController::class, 'search'])->name('products.search');
Route::get('/products/{field}/{text}/{country?}', [IkeaProductsController::class, 'search'])->name('products.search');
Route::post('/products/compare/', [ProductsCompareController::class, 'compare'])->name('products.compare');
Route::get('phpmyinfo', function () {
phpinfo();
})->name('phpmyinfo');
Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified'])->group(function () {
// Route::get('/', function () {
// return Inertia::render('Dashboard');
// })->name('dashboard');
Route::get('/dashboard', function () {
return Inertia::render('Dashboard');
})->name('dashboard');
Route::prefix('/superuser')->name('superuser.')->group(function () {
Route::resource('permission', App\Http\Controllers\Superuser\PermissionController::class)->only([
@@ -75,8 +90,9 @@ Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified']
Route::get('/', 'index')->name('index');
Route::patch('/', 'update')->name('update');
});
Route::get('/activity/login', [App\Http\Controllers\ActivityController::class, 'login'])->name('activity.login');
Route::get('/activity/logging', [App\Http\Controllers\LoggingController::class, 'logging'])->name('activity.logging');
Route::get('/user/{user}/menu', fn (App\Models\User $user) => $user->menus())->name('user.menu');
Route::get('/permission/get', [App\Http\Controllers\Superuser\PermissionController::class, 'get'])->name('permission');
@@ -84,6 +100,7 @@ Route::middleware(['auth:sanctum', config('jetstream.auth_session'), 'verified']
Route::post('/role/paginate', [App\Http\Controllers\Superuser\RoleController::class, 'paginate'])->name('role.paginate');
Route::post('/user/paginate', [App\Http\Controllers\Superuser\UserController::class, 'paginate'])->name('user.paginate');
Route::post('/activity/login', [App\Http\Controllers\ActivityController::class, 'logins'])->name('activity.login.post');
Route::post('/activity/logging', [App\Http\Controllers\LoggingController::class, 'records'])->name('activity.logging.post');
Route::get('/menu/get', [App\Http\Controllers\Superuser\MenuController::class, 'get'])->name('menu');
});
});
});

View File

@@ -1,6 +1,7 @@
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';
import viteCompression from 'vite-plugin-compression';
export default defineConfig({
plugins: [
@@ -15,5 +16,15 @@ export default defineConfig({
},
},
}),
viteCompression(),
],
build: {
assetsInlineLimit: 0
},
esbuild: {
drop: ['console', 'debugger'],
},
resolve: {
dedupe: ["vue"],
}
});