mirror of
https://github.com/Mueller-Patrick/Betterzon.git
synced 2024-12-23 12:15:11 +00:00
Compare commits
14 Commits
fe2c064e30
...
45b5e04442
Author | SHA1 | Date | |
---|---|---|---|
|
45b5e04442 | ||
a792c43e24 | |||
|
78e2de6545 | ||
a6a5b58e25 | |||
012de346e8 | |||
f28b301a28 | |||
|
45acbfd9a2 | ||
|
841502f9d1 | ||
|
c90949de47 | ||
|
7f43d27a79 | ||
|
d83fcdf693 | ||
|
ce92abdb40 | ||
0cd1213c40 | |||
1fd115c2a2 |
|
@ -25,8 +25,8 @@ crawlingstatusRouter.get('/', async (req: Request, res: Response) => {
|
|||
try {
|
||||
// Authenticate user
|
||||
const user_ip = req.connection.remoteAddress ?? '';
|
||||
const session_id = req.body.session_id;
|
||||
const session_key = req.body.session_key;
|
||||
const session_id = (req.query.session_id ?? '').toString();
|
||||
const session_key = (req.query.session_key ?? '').toString();
|
||||
const user = await UserService.checkSession(session_id, session_key, user_ip);
|
||||
|
||||
if (!user.is_admin) {
|
||||
|
|
|
@ -24,8 +24,8 @@ favoriteshopsRouter.get('/', async (req: Request, res: Response) => {
|
|||
try {
|
||||
// Authenticate user
|
||||
const user_ip = req.connection.remoteAddress ?? '';
|
||||
const session_id = req.params.session_id;
|
||||
const session_key = req.params.session_key;
|
||||
const session_id = (req.query.session_id ?? '').toString();
|
||||
const session_key = (req.query.session_key ?? '').toString();
|
||||
const user = await UserService.checkSession(session_id, session_key, user_ip);
|
||||
|
||||
const priceAlarms = await FavoriteShopsService.getFavoriteShops(user.user_id);
|
||||
|
@ -76,8 +76,8 @@ favoriteshopsRouter.delete('/:id', async (req: Request, res: Response) => {
|
|||
try {
|
||||
// Authenticate user
|
||||
const user_ip = req.connection.remoteAddress ?? '';
|
||||
const session_id = req.params.session_id;
|
||||
const session_key = req.params.session_key;
|
||||
const session_id = (req.query.session_id ?? '').toString();
|
||||
const session_key = (req.query.session_key ?? '').toString();
|
||||
const user = await UserService.checkSession(session_id, session_key, user_ip);
|
||||
|
||||
// Get info for price alarm creation
|
||||
|
|
|
@ -24,8 +24,8 @@ pricealarmsRouter.get('/', async (req: Request, res: Response) => {
|
|||
try {
|
||||
// Authenticate user
|
||||
const user_ip = req.connection.remoteAddress ?? '';
|
||||
const session_id = req.params.session_id;
|
||||
const session_key = req.params.session_key;
|
||||
const session_id = (req.query.session_id ?? '').toString();
|
||||
const session_key = (req.query.session_key ?? '').toString();
|
||||
const user = await UserService.checkSession(session_id, session_key, user_ip);
|
||||
|
||||
const priceAlarms = await PriceAlarmsService.getPriceAlarms(user.user_id);
|
||||
|
@ -106,3 +106,29 @@ pricealarmsRouter.put('/', async (req: Request, res: Response) => {
|
|||
res.status(500).send(JSON.stringify({'message': 'Internal Server Error. Try again later.'}));
|
||||
}
|
||||
});
|
||||
|
||||
// DELETE pricealarms/:id
|
||||
pricealarmsRouter.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
// Authenticate user
|
||||
const user_ip = req.connection.remoteAddress ?? '';
|
||||
const session_id = (req.query.session_id ?? '').toString();
|
||||
const session_key = (req.query.session_key ?? '').toString();
|
||||
const user = await UserService.checkSession(session_id, session_key, user_ip);
|
||||
|
||||
const id: number = parseInt(req.params.id, 10);
|
||||
|
||||
const success = await PriceAlarmsService.deletePriceAlarm(id, user.user_id);
|
||||
|
||||
if (success) {
|
||||
res.status(200).send(JSON.stringify({success: true}));
|
||||
return;
|
||||
} else {
|
||||
res.status(500).send(JSON.stringify({success: false}));
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('Error handling a request: ' + e.message);
|
||||
res.status(500).send(JSON.stringify({'message': 'Internal Server Error. Try again later.'}));
|
||||
}
|
||||
});
|
||||
|
|
|
@ -92,3 +92,24 @@ export const updatePriceAlarm = async (alarm_id: number, user_id: number, define
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes the given price alarm
|
||||
* @param alarm_id The id of the price alarm to update
|
||||
* @param user_id The id of the user that wants to update the price alarm
|
||||
*/
|
||||
export const deletePriceAlarm = async (alarm_id: number, user_id: number): Promise<boolean> => {
|
||||
let conn;
|
||||
try {
|
||||
conn = await pool.getConnection();
|
||||
const res = await conn.query('DELETE FROM price_alarms WHERE alarm_id = ? AND user_id = ?', [alarm_id, user_id]);
|
||||
|
||||
return res.affectedRows === 1;
|
||||
} catch (err) {
|
||||
throw err;
|
||||
} finally {
|
||||
if (conn) {
|
||||
conn.end();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -282,11 +282,11 @@ export const getBestDeals = async (amount: number): Promise<Prices> => {
|
|||
'price_in_cents': lowestPrice.price_in_cents,
|
||||
'timestamp': lowestPrice.timestamp,
|
||||
'amazonDifference': (amazonPrice.price_in_cents - lowestPrice.price_in_cents),
|
||||
'amazonDifferencePercent': ((1 - (lowestPrice.price_in_cents / amazonPrice.price_in_cents)) * 100),
|
||||
'amazonDifferencePercent': ((amazonPrice.price_in_cents / lowestPrice.price_in_cents) * 100),
|
||||
};
|
||||
|
||||
// Push only deals were the amazon price is actually higher
|
||||
if (deal.amazonDifferencePercent > 0) {
|
||||
if (deal.amazonDifferencePercent > 0 && deal.amazonDifference > 0) {
|
||||
deals.push(deal as Deal);
|
||||
}
|
||||
}
|
||||
|
|
4
Backend/src/models/vendors/vendors.router.ts
vendored
4
Backend/src/models/vendors/vendors.router.ts
vendored
|
@ -37,8 +37,8 @@ vendorsRouter.get('/managed', async (req: Request, res: Response) => {
|
|||
try {
|
||||
// Authenticate user
|
||||
const user_ip = req.connection.remoteAddress ?? '';
|
||||
const session_id = req.params.session_id;
|
||||
const session_key = req.params.session_key;
|
||||
const session_id = (req.query.session_id ?? '').toString();
|
||||
const session_key = (req.query.session_key ?? '').toString();
|
||||
const user = await UserService.checkSession(session_id, session_key, user_ip);
|
||||
|
||||
const vendors = await VendorService.getManagedShops(user.user_id);
|
||||
|
|
|
@ -8,14 +8,16 @@ import stepdefs.Preconditions;
|
|||
|
||||
@RunWith(Cucumber.class)
|
||||
@CucumberOptions(
|
||||
features = {"src/test/resource/searchProduct.feature",
|
||||
"src/test/resource/priceAlarms.feature"}
|
||||
features = {"src/test/resource/searchProduct.feature",
|
||||
"src/test/resource/priceAlarms.feature",
|
||||
"src/test/resource/favoriteShopList.feature",
|
||||
"src/test/resource/manageVendor.feature"}
|
||||
)
|
||||
|
||||
public class RunTest {
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
Preconditions.driver= new FirefoxDriver();
|
||||
Preconditions.driver = new FirefoxDriver();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
|
|
27
CucumberTests/src/test/java/stepdefs/FavoriteShopList.java
Normal file
27
CucumberTests/src/test/java/stepdefs/FavoriteShopList.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
package stepdefs;
|
||||
|
||||
import io.cucumber.java.en.Given;
|
||||
import io.cucumber.java.en.Then;
|
||||
import io.cucumber.java.en.When;
|
||||
|
||||
public class FavoriteShopList {
|
||||
@Given("^the user has at least (\\d+) favorite shop$")
|
||||
public void the_user_has_at_least_favorite_shop(int arg1) throws Exception {
|
||||
}
|
||||
|
||||
@When("^the user clicks on favorite shops$")
|
||||
public void the_user_clicks_on_favorite_shops() throws Exception {
|
||||
}
|
||||
|
||||
@Then("^he should see his favorite shops list$")
|
||||
public void he_Should_see_his_favorite_shops_list() throws Exception {
|
||||
}
|
||||
|
||||
@When("^he clicks on delete a favorite shop entry$")
|
||||
public void he_clicks_on_delete_a_favorite_shop_enty() throws Exception {
|
||||
}
|
||||
|
||||
@Then("^the favorite shop entry should be deleted$")
|
||||
public void the_favorite_shop_entry_should_be_deleted() throws Exception {
|
||||
}
|
||||
}
|
31
CucumberTests/src/test/java/stepdefs/ManageVendor.java
Normal file
31
CucumberTests/src/test/java/stepdefs/ManageVendor.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
package stepdefs;
|
||||
|
||||
import io.cucumber.java.en.Given;
|
||||
import io.cucumber.java.en.Then;
|
||||
import io.cucumber.java.en.When;
|
||||
|
||||
public class ManageVendor {
|
||||
@Given("^the user is logged in as vendor manager$")
|
||||
public void the_user_is_logged_in_as_vendor_manager() throws Exception {
|
||||
}
|
||||
|
||||
@When("^the user opens the shop managing page$")
|
||||
public void the_user_opens_the_shop_managing_page() throws Exception {
|
||||
}
|
||||
|
||||
@When("^the user clicks on deactivate a listing$")
|
||||
public void the_user_clicks_on_deactivate_a_listing() throws Exception {
|
||||
}
|
||||
|
||||
@Then("^the listing should be deactivated$")
|
||||
public void the_listing_should_be_deactivated() throws Exception {
|
||||
}
|
||||
|
||||
@When("^the user clicks on deactivate the shop$")
|
||||
public void the_user_clicks_on_deactivate_the_shop() throws Exception {
|
||||
}
|
||||
|
||||
@Then("^the shop and all related listings should be deactivated$")
|
||||
public void the_shop_and_all_related_listings_should_be_deactivated() throws Exception {
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ public class SearchProduct {
|
|||
//throw new PendingException();
|
||||
Preconditions.driver.get("https://betterzon.xyz");
|
||||
WebElement logo = (new WebDriverWait(Preconditions.driver, 10))
|
||||
.until(ExpectedConditions.elementToBeClickable(By.cssSelector(".logo")));
|
||||
.until(ExpectedConditions.elementToBeClickable(By.cssSelector(".navbar-brand")));
|
||||
}
|
||||
|
||||
@When("^the user enters the search term \"([^\"]*)\" and clicks search$")
|
||||
|
@ -25,7 +25,7 @@ public class SearchProduct {
|
|||
searchField.sendKeys(searchTerm);
|
||||
searchField.sendKeys(Keys.ENTER);
|
||||
WebElement logo = (new WebDriverWait(Preconditions.driver, 10))
|
||||
.until(ExpectedConditions.elementToBeClickable(By.cssSelector(".logo")));
|
||||
.until(ExpectedConditions.elementToBeClickable(By.cssSelector(".navbar-brand")));
|
||||
}
|
||||
|
||||
@Then("^the user should see the error page \"([^\"]*)\"$")
|
||||
|
|
20
CucumberTests/src/test/resource/favoriteShopList.feature
Normal file
20
CucumberTests/src/test/resource/favoriteShopList.feature
Normal file
|
@ -0,0 +1,20 @@
|
|||
Feature: Favorite Shop List
|
||||
|
||||
Scenario: Access Favorite Shop List
|
||||
Given the user is on the landing page
|
||||
And the user is logged in
|
||||
And the user has at least 1 favorite shop
|
||||
When the user clicks on the profile icon
|
||||
Then the profile details popup should open
|
||||
When the user clicks on favorite shops
|
||||
Then he should see his favorite shops list
|
||||
|
||||
Scenario: Remove Favorite Shop Entry
|
||||
Given the user is on the landing page
|
||||
And the user is logged in
|
||||
And the user has at least 1 favorite shop
|
||||
When the user clicks on the profile icon
|
||||
Then the profile details popup should open
|
||||
When the user clicks on favorite shops
|
||||
And he clicks on delete a favorite shop entry
|
||||
Then the favorite shop entry should be deleted
|
15
CucumberTests/src/test/resource/manageVendor.feature
Normal file
15
CucumberTests/src/test/resource/manageVendor.feature
Normal file
|
@ -0,0 +1,15 @@
|
|||
Feature: Manage Vendor Shop
|
||||
|
||||
Scenario: Deactivate Product Listing
|
||||
Given the user is on the landing page
|
||||
And the user is logged in as vendor manager
|
||||
When the user opens the shop managing page
|
||||
And the user clicks on deactivate a listing
|
||||
Then the listing should be deactivated
|
||||
|
||||
Scenario: Deactivate Shop Completely
|
||||
Given the user is on the landing page
|
||||
And the user is logged in as vendor manager
|
||||
When the user opens the shop managing page
|
||||
And the user clicks on deactivate the shop
|
||||
Then the shop and all related listings should be deactivated
|
|
@ -10,8 +10,8 @@
|
|||
</div>
|
||||
<!-- About Section Content-->
|
||||
<div class="row">
|
||||
<div class="col-lg-4 ms-auto"><p class="lead">text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries</p></div>
|
||||
<div class="col-lg-4 me-auto"><p class="lead">text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries</p></div>
|
||||
<div class="col-lg-4 ms-auto"><p class="lead">You follow the same passion as we do and you want to find alternatives to the de-facto monopolist Amazon?</p></div>
|
||||
<div class="col-lg-4 me-auto"><p class="lead">In this case, welcome aboard! We’re happy that you share our passion and hope that we can help you achieving this goal with the website</p></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="col-md-8 col-xs-12 col-sm-12 login_form ">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<h2>Konto erstellen</h2>
|
||||
<h2>Registration</h2>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form [formGroup]="form" class="form-group" (ngSubmit)="onSubmit()">
|
||||
|
@ -22,17 +22,17 @@
|
|||
</div>
|
||||
<div class="row">
|
||||
<!-- <span class="fa fa-lock"></span> -->
|
||||
<input type="password" formControlName="password" name="password" id="password" class="form__input" placeholder="Kennwort">
|
||||
<input type="password" formControlName="password" name="password" id="password" class="form__input" placeholder="Password">
|
||||
</div>
|
||||
<!--
|
||||
<div class="row">
|
||||
<input type="password" name="password" id="password_repeated" class="form__input" placeholder="Kennwort bestätigen">
|
||||
</div> -->
|
||||
<div class="row">
|
||||
<input type="submit" value="Erstellen" class="btn_signin">
|
||||
<input type="submit" value="Sign up" class="btn_signin">
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="/signin">Sich anmelden</a>
|
||||
<p>Have an account?<a href="/signin">Log In</a></p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="col-md-8 col-xs-12 col-sm-12 login_form ">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<h2>Anmelden</h2>
|
||||
<h2>Sign In</h2>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form [formGroup]="loginForm" class="form-group" (ngSubmit)="onSubmit()">
|
||||
|
@ -18,12 +18,12 @@
|
|||
<input type="password" formControlName="password" name="password" id="password" class="form__input" placeholder="Password">
|
||||
</div>
|
||||
<div class="row">
|
||||
<input type="submit" value="Anmelden" class="btn_signin">
|
||||
<input type="submit" value="Log in" class="btn_signin">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="row">
|
||||
<a href="/registration">Konto erstellen</a>
|
||||
<p>No account yet?<a href="/registration">sign up</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,24 +5,22 @@
|
|||
<div class="col-lg-4 mb-5 mb-lg-0">
|
||||
<h4 class="text-uppercase mb-4">Location</h4>
|
||||
<p class="lead mb-0">
|
||||
70376 Stuttgart
|
||||
76133 Karlsruhe
|
||||
<br />
|
||||
|
||||
</p>
|
||||
</div>
|
||||
<!-- Footer Social Icons-->
|
||||
<div class="col-lg-4 mb-5 mb-lg-0">
|
||||
<h4 class="text-uppercase mb-4">FOLGE UNS</h4>
|
||||
<a class="btn btn-outline-light btn-social mx-1" href="#!"><i class="fab fa-fw fa-github"></i></a>
|
||||
<a class="btn btn-outline-light btn-social mx-1" href="#!"><i class="fab fa-fw fa-twitter"></i></a>
|
||||
<a class="btn btn-outline-light btn-social mx-1" href="#!"><i class="fab fa-fw fa-linkedin-in"></i></a>
|
||||
<a class="btn btn-outline-light btn-social mx-1" href="#!"><i class="fab fa-fw fa-dribbble"></i></a>
|
||||
<h4 class="text-uppercase mb-4">FOLLOW UNS</h4>
|
||||
<a class="btn btn-outline-light btn-social mx-1" href="https://github.com/Mueller-Patrick/Betterzon"><i class="fab fa-fw fa-github"></i></a>
|
||||
<a class="btn btn-outline-light btn-social mx-1" href="https://blog.betterzon.xyz/"><i class="fab fa-fw fa-dribbble"></i></a>
|
||||
</div>
|
||||
<!-- Footer About Text-->
|
||||
<div class="col-lg-4">
|
||||
<h4 class="text-uppercase mb-4">SOME INFO</h4>
|
||||
<h4 class="text-uppercase mb-4">CONTACT US</h4>
|
||||
<p class="lead mb-0">
|
||||
text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries
|
||||
betterzon-contact@mueller-patrick.tech
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<div class="copyright py-4 text-center text-white">
|
||||
<div class="container"><small>Copyright © Your Website 2021</small></div>
|
||||
<div class="container"><small>Copyright © Betterzon 2021</small></div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<section class="page-section portfolio" id="top-gesuchte">
|
||||
<div class="container">
|
||||
<!-- Portfolio Section Heading-->
|
||||
<h2 class="page-section-heading text-center text-uppercase text-secondary mb-0">TOP-ANGEBOTE</h2>
|
||||
<h2 class="page-section-heading text-center text-uppercase text-secondary mb-0">TOP-SEARCHES</h2>
|
||||
<!-- Icon Divider-->
|
||||
<div class="divider-custom">
|
||||
<div class="divider-custom-line"></div>
|
||||
|
|
|
@ -81,14 +81,14 @@ export class HotDealsWidgetComponent implements OnInit {
|
|||
}
|
||||
|
||||
getVendors(): void {
|
||||
this.productsPricesMap.keys().forEach(
|
||||
key => {
|
||||
const currentDeal = this.productsPricesMap[key].lowestPrice;
|
||||
this.bestDealsProductIds.forEach(
|
||||
productId => {
|
||||
const currentDeal = this.productsPricesMap[productId].lowestPrice;
|
||||
this.apiService.getVendorById(currentDeal.vendor_id).subscribe(
|
||||
vendor => {
|
||||
this.productsPricesMap[key].vendor = vendor
|
||||
})
|
||||
})
|
||||
this.productsPricesMap[productId].vendor = vendor;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<section class="page-section portfolio" id="unsere-kunden">
|
||||
<div class="container">
|
||||
<!-- Portfolio Section Heading-->
|
||||
<h2 class="page-section-heading text-center text-uppercase text-secondary mb-0">SIE VERTRAUEN UNS</h2>
|
||||
<h2 class="page-section-heading text-center text-uppercase text-secondary mb-0">THEY TRUST US</h2>
|
||||
<!-- Icon Divider-->
|
||||
<div class="divider-custom">
|
||||
<div class="divider-custom-line"></div>
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
<header class="masthead bg-transparent text-white text-center" id="w1">
|
||||
|
||||
</header>
|
||||
<div class="productItem">
|
||||
<div class="productImageContainer">
|
||||
<img class="productImage" src="https://www.mueller-patrick.tech/betterzon/images/{{product.image_guid}}.jpg"/>
|
||||
|
@ -20,8 +23,12 @@
|
|||
{{product?.short_description}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="priceAlarm" (click)="setPriceAlarm()">
|
||||
Set Price Alarm
|
||||
<div class="priceAlarm" *ngIf="!isLoggedIn" routerLink="/signin">
|
||||
Login to set a price alarm
|
||||
</div>
|
||||
<div class="priceAlarm" *ngIf="isLoggedIn">
|
||||
<input type="search" id="s" name="price" [(ngModel)]="price">
|
||||
<div (click)="setPriceAlarm()">Set Price Alarm</div>
|
||||
</div>
|
||||
<div class="bestPriceContainer">
|
||||
<div class="bestPrice">
|
||||
|
|
|
@ -34,6 +34,8 @@ export class ProductDetailsComponent implements OnInit {
|
|||
vendorMap = {};
|
||||
@ViewChild('chart') chart: ChartComponent;
|
||||
public chartOptions: ChartOptions;
|
||||
isLoggedIn: boolean;
|
||||
price: any;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService
|
||||
|
@ -44,6 +46,9 @@ export class ProductDetailsComponent implements OnInit {
|
|||
this.getProduct();
|
||||
this.getVendors();
|
||||
this.getPrices();
|
||||
if (this.apiService.getSessionInfoFromLocalStorage().session_id != "") {
|
||||
this.isLoggedIn = true;
|
||||
}
|
||||
}
|
||||
|
||||
getProduct(): void {
|
||||
|
@ -119,8 +124,10 @@ export class ProductDetailsComponent implements OnInit {
|
|||
}
|
||||
|
||||
setPriceAlarm() {
|
||||
this.apiService.createPriceAlarms(this.productId, 9).subscribe(
|
||||
this.apiService.createPriceAlarms(this.productId, this.price*100).subscribe(
|
||||
alarms => console.log(alarms)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,6 +1,30 @@
|
|||
<div *ngIf="products.length==0">
|
||||
No Products found!
|
||||
</div>
|
||||
<div class="container mt-5 mb-5">
|
||||
<div class="d-flex justify-content-center row">
|
||||
<div class="col-md-10">
|
||||
<div class="row p-2 bg-white border rounded" *ngFor="let product of products">
|
||||
<div class="col-md-3 mt-1"><img width="50%" class="img-fluid img-responsive rounded product-image" src="https://www.mueller-patrick.tech/betterzon/images/{{product.image_guid}}.jpg"></div>
|
||||
<div class="col-md-6 mt-1">
|
||||
<h5>{{product.name}}</h5>
|
||||
<div class="d-flex flex-row">
|
||||
<p class="text-justify text-truncate para mb-0">{{product.short_description}}</p>
|
||||
</div>
|
||||
<div class="mt-1 mb-1 spec-1"><span></span><span class="dot"></span><span></span><span class="dot"></span><span><br></span></div>
|
||||
<div class="mt-1 mb-1 spec-1"><span></span><span class="dot"></span><span></span><span class="dot"></span><span><br></span></div>
|
||||
</div>
|
||||
<div class="align-items-center align-content-center col-md-3 border-left mt-1">
|
||||
<div class="d-flex flex-row align-items-center">
|
||||
<h4 class="mr-1">${{pricesMap[product.product_id]?.price_in_cents/100}}</h4>
|
||||
</div>
|
||||
<div class="d-flex flex-column mt-4"><button class="btn btn-primary btn-sm" type="button" (click)="clickedProduct(product)">Details</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="productItem" *ngFor="let product of products" (click)="clickedProduct(product)">
|
||||
<div class="productImageContainer" *ngIf="showProductPicture===true">
|
||||
<img class="productImage" src="https://www.mueller-patrick.tech/betterzon/images/{{product.image_guid}}.jpg"/>
|
||||
|
@ -20,3 +44,4 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
|
|
@ -10,6 +10,7 @@ import {ActivatedRoute, Router} from '@angular/router';
|
|||
})
|
||||
export class ProductListComponent implements OnInit {
|
||||
products: Product[] = [];
|
||||
pricesMap: any = {};
|
||||
@Input() numberOfProducts: number;
|
||||
@Input() showProductPicture: boolean;
|
||||
@Input() searchQuery: string;
|
||||
|
@ -53,15 +54,35 @@ export class ProductListComponent implements OnInit {
|
|||
}
|
||||
|
||||
getProducts(): void {
|
||||
this.apiService.getProducts().subscribe(products => this.products = products);
|
||||
this.apiService.getProducts().subscribe(products => {
|
||||
this.products = products;
|
||||
this.getPrices();
|
||||
});
|
||||
}
|
||||
|
||||
getPrices(): void {
|
||||
this.products.forEach(
|
||||
product => {
|
||||
this.apiService.getLowestPrices(product.product_id).subscribe(
|
||||
prices => {
|
||||
this.pricesMap[product.product_id] = prices[prices.length - 1];
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
getSearchedProducts(): void {
|
||||
this.apiService.getProductsByQuery(this.searchQuery).subscribe(products => this.products = products);
|
||||
this.apiService.getProductsByQuery(this.searchQuery).subscribe(products => {
|
||||
this.products = products;
|
||||
this.getPrices();
|
||||
});
|
||||
}
|
||||
|
||||
clickedProduct(product: Product): void {
|
||||
this.router.navigate([('/product/' + product.product_id)]);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
.inf-content{
|
||||
border:1px solid #DDDDDD;
|
||||
-webkit-border-radius:10px;
|
||||
-moz-border-radius:10px;
|
||||
border-radius:10px;
|
||||
box-shadow: 7px 7px 7px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.header-in-page {
|
||||
padding-top: calc(1rem + 20px);
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
table, th, td {
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
.delete:hover {
|
||||
cursor: pointer;
|
||||
color: #0d5a4b;
|
||||
}
|
|
@ -1,3 +1,104 @@
|
|||
<div class="container bootstrap snippets bootdey">
|
||||
<div class="panel-body inf-content">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<img alt="" style="width:600px;" title="" class="img-circle img-thumbnail isTooltip" src="https://bootdey.com/img/Content/avatar/avatar7.png" data-original-title="Usuario">
|
||||
<ul title="Ratings" class="list-inline ratings text-center">
|
||||
<li><a href="#"><span class="glyphicon glyphicon-star"></span></a></li>
|
||||
<li><a href="#"><span class="glyphicon glyphicon-star"></span></a></li>
|
||||
<li><a href="#"><span class="glyphicon glyphicon-star"></span></a></li>
|
||||
<li><a href="#"><span class="glyphicon glyphicon-star"></span></a></li>
|
||||
<li><a href="#"><span class="glyphicon glyphicon-star"></span></a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<strong>Information</strong><br>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-user-information">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>
|
||||
<span class="glyphicon glyphicon-bookmark text-primary"></span>
|
||||
Username
|
||||
</strong>
|
||||
</td>
|
||||
<td class="text-primary">
|
||||
{{currentUser.username}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
<strong>
|
||||
<span class="glyphicon glyphicon-eye-open text-primary"></span>
|
||||
Role
|
||||
</strong>
|
||||
</td>
|
||||
<td class="text-primary">
|
||||
User
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>
|
||||
<span class="glyphicon glyphicon-envelope text-primary"></span>
|
||||
Email
|
||||
</strong>
|
||||
</td>
|
||||
<td class="text-primary">
|
||||
{{currentUser.email}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<strong>
|
||||
<span class="glyphicon glyphicon-calendar text-primary"></span>
|
||||
created
|
||||
</strong>
|
||||
</td>
|
||||
<td class="text-primary">
|
||||
{{currentUser.registration_date}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<header class="header-in-page">
|
||||
|
||||
</header>
|
||||
<div class="container bootstrap snippets bootdey">
|
||||
<div class="col-auto">
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<th>Product</th>
|
||||
<th>Price</th>
|
||||
<th>Change</th>
|
||||
<th>Delete</th>
|
||||
</tr>
|
||||
<tr *ngFor="let alarm of alarms">
|
||||
<td>
|
||||
{{productsMap[alarm.product_id]?.name}}
|
||||
</td>
|
||||
<td>
|
||||
{{alarm.defined_price/100}}
|
||||
</td>
|
||||
<td>
|
||||
<img class="delete" src="../assets/images/Delete_icon-icons.com_55931.png" (click)="delete(alarm.alarm_id)">
|
||||
</td>
|
||||
<td>
|
||||
<img class="delete" src="../assets/images/Delete_icon-icons.com_55931.png" (click)="delete(alarm.alarm_id)">
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="container" *ngIf="currentUser; else loggedOut">
|
||||
<p>
|
||||
<strong>e-mail</strong>
|
||||
|
@ -33,4 +134,4 @@
|
|||
<ng-template #loggedOut>
|
||||
Please login.
|
||||
</ng-template>
|
||||
|
||||
-->
|
||||
|
|
|
@ -49,4 +49,10 @@ export class ProfileComponent implements OnInit {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
delete(id:number): void {
|
||||
this.api.deletePriceAlarm(id).subscribe(
|
||||
res => window.location.reload()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,13 +10,13 @@
|
|||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarResponsive">
|
||||
<ul class="navbar-nav ms-auto">
|
||||
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded" href="#top-gesuchte">Top-Gesuchte</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded" href="#about">über uns</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded" href="#unsere-kunden">Unsere Kunden</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1" *ngIf="!isLoggedIn"><a class="nav-link py-3 px-0 px-lg-3 rounded" routerLink="/signin">Anmelden</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1" *ngIf="!isLoggedIn"><a class="nav-link py-3 px-0 px-lg-3 rounded" routerLink="/registration">Konto Erstellen</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1" *ngIf="isLoggedIn"><a class="nav-link py-3 px-0 px-lg-3 rounded" routerLink="" (click)="logout()">Log Out</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1" *ngIf="isLoggedIn"><a class="nav-link py-3 px-0 px-lg-3 rounded" routerLink="/profile">Profile</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded" href="#top-gesuchte">top-searches</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded" href="#about">about</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1"><a class="nav-link py-3 px-0 px-lg-3 rounded" href="#unsere-kunden">our clients</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1" *ngIf="!isLoggedIn"><a class="nav-link py-3 px-0 px-lg-3 rounded" routerLink="/signin">sign in</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1" *ngIf="!isLoggedIn"><a class="nav-link py-3 px-0 px-lg-3 rounded" routerLink="/registration">sign up</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1" *ngIf="isLoggedIn"><a class="nav-link py-3 px-0 px-lg-3 rounded" routerLink="" (click)="logout()">log out</a></li>
|
||||
<li class="nav-item mx-0 mx-lg-1" *ngIf="isLoggedIn"><a class="nav-link py-3 px-0 px-lg-3 rounded" routerLink="/profile">profile</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,9 +4,9 @@ import {Router} from "@angular/router";
|
|||
|
||||
|
||||
@Component({
|
||||
selector: 'app-top-bar',
|
||||
templateUrl: './top-bar.component.html',
|
||||
styleUrls: ['./top-bar.component.css']
|
||||
selector: 'app-top-bar',
|
||||
templateUrl: './top-bar.component.html',
|
||||
styleUrls: ['./top-bar.component.css']
|
||||
})
|
||||
export class TopBarComponent implements OnInit {
|
||||
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
#mainComponents {
|
||||
margin: 5em;
|
||||
margin-top: .5em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
#productListsContainer {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
'search search'
|
||||
'popularSearches bestDeals';
|
||||
grid-template-columns: 50% 50%;
|
||||
}
|
||||
|
||||
#searchContainer {
|
||||
position: relative;
|
||||
grid-area: search;
|
||||
height: 10em;
|
||||
}
|
||||
|
||||
#searchContainer input {
|
||||
position: relative;
|
||||
font-size: 1.5em;
|
||||
padding: .25em;
|
||||
display: block;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
margin: auto;
|
||||
-ms-transform: translateY(50%);
|
||||
transform: translateY(2.5em);
|
||||
}
|
||||
|
||||
#popularSearchesList {
|
||||
grid-area: popularSearches;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
#popularSearchesList h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#bestDealsList {
|
||||
grid-area: bestDeals;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
#bestDealsList h2 {
|
||||
text-align: center;
|
||||
}
|
|
@ -1,5 +1,43 @@
|
|||
#mainComponents {
|
||||
margin: 5em;
|
||||
margin-top: .5em;
|
||||
margin-bottom: .5em;
|
||||
body {
|
||||
background: #eee
|
||||
}
|
||||
|
||||
.ratings i {
|
||||
font-size: 16px;
|
||||
color: red
|
||||
}
|
||||
|
||||
.strike-text {
|
||||
color: red;
|
||||
text-decoration: line-through
|
||||
}
|
||||
|
||||
.product-image {
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
}
|
||||
|
||||
.dot {
|
||||
height: 7px;
|
||||
width: 7px;
|
||||
margin-left: 6px;
|
||||
margin-right: 6px;
|
||||
margin-top: 3px;
|
||||
background-color: blue;
|
||||
border-radius: 50%;
|
||||
display: inline-block
|
||||
}
|
||||
|
||||
.spec-1 {
|
||||
color: #938787;
|
||||
font-size: 15px
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-weight: 400
|
||||
}
|
||||
|
||||
.para {
|
||||
font-size: 16px
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,14 @@
|
|||
<app-top-bar></app-top-bar>
|
||||
<header class="masthead bg-transparent text-white text-center" id="w1">
|
||||
|
||||
</header>
|
||||
|
||||
<div id="mainComponents">
|
||||
<app-product-list numberOfProducts="20" [showProductPicture]="true" searchQuery="{{searchTerm}}"
|
||||
type="search"></app-product-list>
|
||||
</div>
|
||||
|
||||
<header class="masthead bg-transparent text-white text-center">
|
||||
|
||||
</header>
|
||||
<app-bottom-bar></app-bottom-bar>
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.header-in-page {
|
||||
padding-top: calc(2rem + 20px);
|
||||
padding-bottom: 6rem;
|
||||
}
|
|
@ -1,3 +1,10 @@
|
|||
<app-top-bar></app-top-bar>
|
||||
<header class="header-in-page">
|
||||
|
||||
</header>
|
||||
<app-profile></app-profile>
|
||||
<header class="header-in-page">
|
||||
|
||||
</header>
|
||||
<app-bottom-bar></app-bottom-bar>
|
||||
<app-copyright></app-copyright>
|
||||
|
|
|
@ -445,6 +445,25 @@ export class ApiService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given price alarm
|
||||
* @param alarmId the price alarm to delete
|
||||
* @return Observable<any> The observable response of the api
|
||||
*/
|
||||
deletePriceAlarm(alarmId: number): Observable<any> {
|
||||
try {
|
||||
const sessionInfo = this.getSessionInfoFromLocalStorage();
|
||||
|
||||
let params = new HttpParams();
|
||||
params = params.append('session_id', sessionInfo.session_id);
|
||||
params = params.append('session_key', sessionInfo.session_key);
|
||||
|
||||
return this.http.delete((this.apiUrl + '/pricealarms/' + alarmId), {params});
|
||||
} catch (exception) {
|
||||
process.stderr.write(`ERROR received from ${this.apiUrl}: ${exception}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* __ __
|
||||
/ / / /_______ __________
|
||||
|
|
BIN
Frontend/src/assets/images/Delete_icon-icons.com_55931.png
Normal file
BIN
Frontend/src/assets/images/Delete_icon-icons.com_55931.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 239 B |
|
@ -447,8 +447,8 @@ progress {
|
|||
}
|
||||
|
||||
.lead {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 300;
|
||||
font-size: 1.30rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.display-1 {
|
||||
|
|
|
@ -5,7 +5,7 @@ Wiki: https://github.com/Mueller-Patrick/Betterzon/wiki
|
|||
|
||||
# Code Quality
|
||||
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/88e47ebf837b43af9d12147c22f77f7f)](https://www.codacy.com/gh/Mueller-Patrick/Betterzon/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Mueller-Patrick/Betterzon&utm_campaign=Badge_Grade)
|
||||
[![Code Coverage](https://img.shields.io/badge/coverage-82%25-green)](https://ci.betterzon.xyz)
|
||||
[![Code Coverage](https://img.shields.io/badge/coverage-71%25-green)](https://ci.betterzon.xyz)
|
||||
|
||||
# Project Status
|
||||
[![Website Status](https://img.shields.io/website?label=www.betterzon.xyz&style=for-the-badge&url=https%3A%2F%2Fwww.betterzon.xyz)](https://www.betterzon.xyz)
|
||||
|
|
|
@ -1 +1 @@
|
|||
<mxfile host="app.diagrams.net" modified="2020-12-03T09:39:52.243Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36" etag="RUzsjnuqX_zRTCHSTruo" version="13.10.6" type="github"><diagram id="QFWcWedTnleHV76omDGD" name="Page-1">5Vzbdps6EP0aPzYLxMX40Una9JbTNG5Pc56yZFBsWkCukGM7X38kDBgkxXYxt6QPyTJjkGHPnotGIwbGRbi+InAxv8YeCgZA89YD43IAwNDR2H8u2GQCcyuYEd/bivSdYOI/oVSYXjdb+h6KSydSjAPqL8pCF0cRcmlJBgnBq/JpDzgo/+oCzpAkmLgwkKU/fI/Ot1IHDHfy98ifzbNf1u3R9psQZienTxLPoYdXBZHxdmBcEIzp9lO4vkABxy7DZXvdu2e+zW+MoIgec8HTFQDvv94+EF03rj9u7qO7n5dv0lEeYbBMH/gWzfyYIoI8Jv8eI5LePd1kkLAHWfCPyzAYuxSTgXH+iAj1GWif4RQFNzj2qY8jdsoUU4rDwgnjwJ/xLyheMOmchgE70NlH+WmyW2NXonVBlD7dFcIhomTDTsm+NVKkN8Lxaqe4TDQv6MxOZTClyiwfeYcm+5AC+gfgmhK4EpYo8sacpewowhE6EhPklUgrI1J4YkvxxJmMoABS/7FMdRUM6S/cYJ/dSQ440MqAOwKQMV4SF6UXFZkpjGOb+8ehkMwQlcZJdJI/dHU1WX+ZmkZ6RT0d0nfDerJfuZ5EM6isp47tKbvtgqImCBJ3nsRfFk+0G4K9pUtl9QUBi+tcbau5T9FkAV3+zYplFmVVPvhBcIEDHnvYdYYHkfPgMnlMCf6FCt/YroOmD/XEF6BbJVgNS44vOtjDj9oDjC6H79dlEqLHyeE91SSkgZq2CSBpaoooS7OecHS23jxJamOcpGVdlbmd6rJoCKkIpimWy3SKiCL3Cn3PC54zMoKXkYf4o2j1WI0xMsteTTYaU0EgUT/12YwhaeJ7RF5w0is4JdOS8G016dWHh/AdADvg3J4ylO0Z/zSZM2SA9mUVtYU8XtLAj5g1ZbPGmtguqsMxZbq3q46RHIwx4Z5xym97QXxm/ZXjcA2ImVoZMZWDaDeqZkH0GcgC7MKEb31CrftcBMh+NQ5hAlucoNcjtEy9c7TkSednPPO7JVVedMsLKHIsaRkmOZicL2PmuOOYSbelKtK9NYozA0cRhFsGzpGAkxHaNzNgYJDNXRoYk4P/+MGZlR1erotfXm4OxdBtpn5EYrbNxPedmILWkzmKZZaVb2sV5yjWqDyQJbKj4TkKkFOF3nPmSMpk7rYnlAGCn7WsaowZCuNowjgNEyZLZOsvPxxW/PBYzQ97pXnJWegVVS85C7tl3Z9aekJrn94VPhdcBTvaeQp+kDmKDhyMdmxQGvWbZy81KBnyZKzjoHTY5YBeUcEQK0TDii5HHwqzKLNlKsgzzGouRzsbjUDJ7djmfr/DDm4Q8dkT8Ipq331RNhXvCQFfjy+SF/RjxO8vraJpMIAklDnZaqWjb1N449Tl9fYNLZstHDS0jBC9MbSy8gGo6OnzamLGIttoytDeT8/B+ONvffkt+nD/e6Kvvpmf9nQldWpcYpnHVDQYNWZcSqBO7TDqR5VnHwd6Ylo6EEKPmPtUTqJGjcUwJaynNtH0t8bTs7KgI/hicYXvaMIIXY1A7ApqmDByUfD2b13MtRVLk00t5qp9ouzvr2HEO5iBNnZdvIy6XWcTA6Rtdx0gdTn7HC8WAb/nbSuazN8byOnmL/q3rDTsPN/QG+vBLIaBfX7ocN7Qr5wc2IJJVE0cDIEMVsuJg6Kvp0eZw2mU6dfqkFDlcKr2POpC3WU4bIwx439s5zK8/2pot/96v6Pruyfz65sjynVZOuCHyTacIl0qpgXJSON4sUsE0rHZb8zYY7jMfSddKe+uECT3OnDW7O9sEc3qSBjEWYLCYauIU0fCoFSBnC9MNix1k2tUcrvp8W2kBMX+E5wmQ3HAF5xQyZNY5wPrko+1pEyBydauutoehSVUS5Ga2Qqk62grVSIte8cLAlf8Mp4Px73KI6w2i4JKtHrdnbLPmRVjyT4e9CSUiIoXFXp0+7wwjljBbziSKHaU/CDMarhCMPt3CSmcwrjbRlZjNOqZlZ28PaTKGn11E1O0dOxlQ09sTFzekoyj6vKW2dzylhpXINHlWcPKEjd3E/iRh4hx2MSm250ln6e5ALq/Zsl+ky/bAk4qzzIFzrNONnpZ9jM12gPJhbjPrj5DlvO4WwQ9JnkgLBeuwwF2ArToMk1HBrpll1mpwOHBeJ7smdLLoHL5DeTbvKJEAjQjhzR7mwBo3c9mbDrsaEGvHa1ddb+56Ghto2VHW6mU8sJIpmhWeREkE6bww6rVF0tY6LNbrr4AOWceABuG3O8nFe/ykR+5wdJDuXBWOiU9EjnKVDZJD1VztJzBeIEiRQUhL+WUqB3wgs95nhsIu1tfbtvoXl/8J+zX7Xqobjo1+VP7kGOukeqfrp782ysYP9Jfn7/d2T+nWqRoFznCnUqTP0EFCsV3toZQ1ws8Dr5homFFyRMMgkL8yOfxtbbQSRnoUcp8poUurxm2kpSqYKur77U9n3nYliq24T1Pq66M0xA2TVZu9DBsYaCWjbPh5taqE8yTbNkSbDlfAuvMluV+hAZaiGvFzGlza7kSs3qaDvob302npvguDjRsboeGUlGntgi8NEVJ+FZNxNpWlLz+dhPAKEo6+rZ9USblr+uaoBAp26779Hqi07I9QaUOUKzaNLaSrNKN3HD5IVwETBEML66f5CUk1+zukCJQvFq9DFWvVKhJL+xw9xrWrYnt3mVrvP0f</diagram></mxfile>
|
||||
<mxfile host="Electron" modified="2021-06-15T10:10:23.336Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/13.4.5 Chrome/83.0.4103.122 Electron/9.1.0 Safari/537.36" etag="xPXaWnl_FRrq1LtBykUU" version="13.4.5" type="device"><diagram id="QFWcWedTnleHV76omDGD" name="Page-1">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</diagram></mxfile>
|
Binary file not shown.
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 85 KiB |
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user