Skip to content

Commit

Permalink
Feature favorites
Browse files Browse the repository at this point in the history
  • Loading branch information
megastary committed Oct 25, 2023
1 parent 596c055 commit 82538c3
Show file tree
Hide file tree
Showing 10 changed files with 174 additions and 24 deletions.
9 changes: 8 additions & 1 deletion models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,14 @@ const userSchema = new Schema({
sendDailyReport: {
type: Boolean,
default: true
}
},
favorites: [
{
type: Schema.Types.ObjectId,
ref: 'Product',
required: false
}
]
})

const model = mongoose.model('User', userSchema)
Expand Down
22 changes: 22 additions & 0 deletions public/javascripts/favorite.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
async function set_favorite(element) {
// Instantly change UI, call database update in the background
var productCard = document.getElementById(element.value)
element.checked
? productCard.classList.add('order-first')
: productCard.classList.remove('order-first')

// Write preference to database
await fetch('/profile', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'favorite',
value: {
product: element.value,
state: element.checked
}
})
})
}
35 changes: 35 additions & 0 deletions public/stylesheets/style.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
/* Favorite button */
input.favorite {
visibility: hidden;
}

input.favorite:checked:after,
input.favorite::after {
display: inline-block;
font-style: normal;
font-variant: normal;
text-rendering: auto;
-webkit-font-smoothing: antialiased;
font-family: "Font Awesome 6 Free";
font-weight: 400;
content: "\f005";
visibility: visible;
top: -0.30em;
position: relative;
}

/* When checked, change color and to fa-solid */
input.favorite:checked:after {
font-weight: 900;
color: gold;
}

/* Kiosk favorite */
i.favorite {
color: gold;
position: absolute;
right: -0.5em;
top: -0.5em;
font-size: 2.5em;
}

/* Limit product header min height */
.card-header-h4-min-height {
min-height: calc((1.275rem + .3vw) * 3);
Expand Down
1 change: 1 addition & 0 deletions routes/edit_product.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ router.get('/', ensureAuthenticated, function (req, res) {
)

Category.find()
.sort([['name', 1]])
.then((categories) => {
logger.debug(
`server.routes.editproduct.get__Successfully loaded ${categories.length} categories.`,
Expand Down
23 changes: 23 additions & 0 deletions routes/kiosk_shop.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ function renderPage(req, res, alert, customer) {
])
.then((docs) => {
Category.find()
.sort([['name', 1]])
.then((categories) => {
logger.debug(
`server.routes.kioskshop.get__Successfully loaded ${categories?.length} categories.`,
Expand All @@ -79,6 +80,28 @@ function renderPage(req, res, alert, customer) {
}
}
)

// Populate favorites if user has any
if (customer.favorites?.length > 0) {
logger.debug(
`server.routes.shop.get__User has set favorites, populating products.`,
{
metadata: {
result: customer.favorites
}
}
)

// Add favorite parameter to products object
customer.favorites.forEach((elFav) => {
docs.forEach((elProd) => {
if (elFav._id.equals(elProd._id)) {
elProd.favorite = true
}
})
})
}

res.render('shop/kiosk_shop', {
title: 'Kiosek | Lednice IT',
products: docs,
Expand Down
1 change: 1 addition & 0 deletions routes/new_product.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ router.get('/', ensureAuthenticated, function (req, res) {
delete req.session.alert
}
Category.find()
.sort([['name', 1]])
.then((categories) => {
logger.debug(
`server.routes.newproduct.get__Successfully loaded ${categories?.length} categories.`,
Expand Down
50 changes: 44 additions & 6 deletions routes/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ router.get('/', ensureAuthenticated, checkKiosk, function (req, res, _next) {
})

router.post('/', ensureAuthenticated, function (req, res, _next) {
var newValue = req.body.value
const newValue = req.body.value
if (req.body.name === 'checkAllProducts') {
User.findByIdAndUpdate(
req.user.id,
Expand Down Expand Up @@ -105,13 +105,13 @@ router.post('/', ensureAuthenticated, function (req, res, _next) {
return
})
} else if (req.body.name === 'realtime-iban') {
if (/^CZ\d{22}$/.test(req.body.value)) {
if (/^CZ\d{22}$/.test(newValue)) {
User.findByIdAndUpdate(req.user.id, {
IBAN: req.body.value
IBAN: newValue
})
.then(() => {
logger.info(
`server.routes.profile.post__User:[${req.user.displayName}] set new IBAN:[${req.body.value}].`,
`server.routes.profile.post__User:[${req.user.displayName}] set new IBAN:[${newValue}].`,
{
metadata: {
result: req.user
Expand All @@ -123,7 +123,7 @@ router.post('/', ensureAuthenticated, function (req, res, _next) {
})
.catch((err) => {
logger.error(
`server.routes.profile.post__Failed to set IBAN:[${req.body.value}] for user:[${req.user.displayName}].`,
`server.routes.profile.post__Failed to set IBAN:[${newValue}] for user:[${req.user.displayName}].`,
{
metadata: {
error: err.message
Expand All @@ -135,7 +135,7 @@ router.post('/', ensureAuthenticated, function (req, res, _next) {
})
} else {
logger.warn(
`server.routes.profile.post__User:[${req.user.displayName}] tried to set invalid IBAN:[${req.body.value}].`,
`server.routes.profile.post__User:[${req.user.displayName}] tried to set invalid IBAN:[${newValue}].`,
{
metadata: {
result: req.user
Expand All @@ -145,6 +145,44 @@ router.post('/', ensureAuthenticated, function (req, res, _next) {
res.status(400).send()
return
}
} else if (req.body.name === 'favorite') {
console.log(newValue?.state)
if (newValue?.state) {
console.log('adding')
}
const operation = newValue?.state ? '$push' : '$pull'
console.log(operation)
User.findByIdAndUpdate(req.user.id, {
[operation]: {
favorites: newValue?.product
}
})
.then(() => {
logger.info(
`server.routes.profile.post__User:[${req.user.displayName}] added/removed favorite:[${newValue?.product}].`,
{
metadata: {
result: req.user
}
}
)
res.status(200).send()
return
})
.catch((err) => {
logger.error(
`server.routes.profile.post__Failed to add/remove favorite for user:[${req.user.displayName}].`,
{
metadata: {
error: err.message
}
}
)
res.status(400).send()
return
})
} else {
res.status(400).send()
}
})

Expand Down
23 changes: 22 additions & 1 deletion routes/shop.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function renderPage(req, res, alert) {
category: '$category',
stock: {
$filter: {
// We filter only the stock object from array where ammount left is greater than 0
// We filter only the stock object from array where amount left is greater than 0
input: '$stock',
as: 'stock',
cond: {
Expand Down Expand Up @@ -84,6 +84,27 @@ function renderPage(req, res, alert) {
}
)

// Populate favorites if user has any
if (req.user.favorites?.length > 0) {
logger.debug(
`server.routes.shop.get__User has set favorites, populating products.`,
{
metadata: {
result: req.user.favorites
}
}
)

// Add favorite parameter to products object
req.user.favorites.forEach((elFav) => {
docs.forEach((elProd) => {
if (elFav._id.equals(elProd._id)) {
elProd.favorite = true
}
})
})
}

res.render('shop/shop', {
title: 'E-shop | Lednice IT',
products: docs,
Expand Down
3 changes: 2 additions & 1 deletion views/shop/kiosk_shop.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
{{/if}}
<div class="row">
{{#each products }}
<div class="col-12 col-md-6 col-xl-3 col-xxxl-2 py-3 collapse show" data-category="{{this.category.0._id}}">
<div class="col-12 col-md-6 col-xl-3 col-xxxl-2 py-3 collapse show {{#if this.favorite}}order-first{{/if}}" id="{{this._id}}" data-category="{{this.category.0._id}}">
<form id="shop_buy_product_{{this.stock.0._id}}" class="needs-validation h-100"
method="POST" action="/kiosk_shop" novalidate>
<div class="card h-100 text-center">
Expand All @@ -46,6 +46,7 @@
<div class="card-body">
<img src="{{ this.imagePath }}" class="mx-auto my-2 img-fluid shop-image-thumbnail"
alt="{{ this.description }}">
{{#if this.favorite}}<i class="fa-solid fa-star favorite"></i>{{/if}}
</div>
<div class="card-footer">
<div class="row">
Expand Down
31 changes: 16 additions & 15 deletions views/shop/shop.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -17,53 +17,53 @@
{{/if}}
<div class="row">
{{#each products }}
<div class="col-12 col-md-6 col-xl-3 col-xxxl-2 py-3 collapse show" data-category="{{this.category.0._id}}">
<div class="col-12 col-md-6 col-xl-3 col-xxxl-2 py-3 collapse show {{#if this.favorite}}order-first{{/if}}" id="{{this._id}}" data-category="{{this.category.0._id}}">
<form id="shop_buy_product_{{this.stock.0._id}}" class="needs-validation h-100" method="POST" action="/shop"
novalidate>
<div class="card h-100 text-center">
<div class="card-header h4 card-header-h4-min-height d-flex align-items-center justify-content-center">
{{ this.displayName }}
{{this.displayName}}
</div>
<div style="height: 0.3rem;{{#if this.category.0.color}}background-color:{{this.category.0.color}};{{/if}}">
</div>
<div class="card-body">
<img src="{{ this.imagePath }}" class="mx-auto my-2 img-fluid shop-image-thumbnail" alt="{{ this.description }}">
<p class="card-text description">{{ this.description }}</p>
<img src="{{this.imagePath}}" class="mx-auto my-2 img-fluid shop-image-thumbnail" alt="{{this.description}}">
<p class="card-text description">{{this.description}}</p>
</div>
<div class="card-footer">
<div class="row">
<div class="col-6 text-start">
{{#if this.stockSum}}
<span class="d-inline-block">
<i class="fa-solid fa-boxes"></i> {{ this.stockSum }}
<i class="fa-solid fa-boxes"></i> {{this.stockSum}}
</span>
{{else}}
<i class="fa-solid fa-boxes"></i> <span class="text-danger">0</span>
{{/if }}
</div>
<div class="col-6 text-end">
<i class="fa-solid fa-star"></i>
<input class="form-check-input favorite" {{#if this.favorite}}checked{{/if}} type="checkbox" value="{{this._id}}" onclick="set_favorite(this)">
</div>
</div>
</div>
<div class="card-footer">
<div class="row">
<div class="col-6 text-start px-0">
{{#if this.stock.0.price }}
<div class="btn btn-lg py-0">{{ this.stock.0.price }} Kč</div>
<input type="hidden" name="product_price" class="btn" value="{{ this.stock.0.price }}">
{{#if this.stock.0.price}}
<div class="btn btn-lg py-0">{{this.stock.0.price}} Kč</div>
<input type="hidden" name="product_price" class="btn" value="{{this.stock.0.price}}">
{{/if}}
</div>
<div class="col-6 text-end px-0">
<input type="submit" class="btn btn-lg py-0 float-end" value="Koupit" {{#unless this.stock.0.amount_left}}
disabled {{/unless}}>
</div>
</div>
<input type="hidden" name="product_id" value="{{ this.stock.0._id }}">
<input type="hidden" name="user_id" value="{{ ../user.id }}">
<input type="hidden" name="display_name" value="{{ this.displayName }}">
<input type="hidden" name="image_path" value="{{ this.imagePath }}">
<input type="hidden" name="_csrf" value="{{ ../csrfToken }}">
<input type="hidden" name="product_id" value="{{this.stock.0._id}}">
<input type="hidden" name="user_id" value="{{../user.id}}">
<input type="hidden" name="display_name" value="{{this.displayName}}">
<input type="hidden" name="image_path" value="{{this.imagePath}}">
<input type="hidden" name="_csrf" value="{{../csrfToken}}">
</div>
</div>
</form>
Expand All @@ -72,4 +72,5 @@
</div>

<script src="/javascripts/form_validate.js"></script>
<script src="/javascripts/category_filter.js"></script>
<script src="/javascripts/category_filter.js"></script>
<script src="/javascripts/favorite.js"></script>

0 comments on commit 82538c3

Please sign in to comment.