Compare commits
8
Commits
cb02bbacf8
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08a0bdcdb8 | ||
|
|
93c132b0df | ||
|
|
771d95d1cd | ||
|
|
254736356b | ||
|
|
738e406725 | ||
|
|
de4630635d | ||
|
|
6b358011b0 | ||
|
|
8729098f20 |
@@ -100,7 +100,7 @@ export class StorefrontCartController {
|
||||
text: 'Added to cart.'
|
||||
});
|
||||
|
||||
res.redirect(HttpStatus.FOUND, safeInternalShopRedirectPath(req));
|
||||
res.redirect(HttpStatus.FOUND, this.buildAddToCartRedirectPath(req, variantId));
|
||||
}
|
||||
|
||||
@Post('shop/cart/product/update')
|
||||
@@ -189,4 +189,18 @@ export class StorefrontCartController {
|
||||
this.cartCookieService.setCart(req, res, cartMutation);
|
||||
}
|
||||
}
|
||||
|
||||
private buildAddToCartRedirectPath(req: Request, variantId: string): string {
|
||||
const redirectPath = safeInternalShopRedirectPath(req);
|
||||
const pathname = redirectPath.split('?')[0] ?? redirectPath;
|
||||
const isProductsIndexPath = pathname === '/' || pathname.startsWith('/shop/categories/');
|
||||
|
||||
if (!isProductsIndexPath) {
|
||||
return redirectPath;
|
||||
}
|
||||
|
||||
const anchor = `product-card-${variantId}`;
|
||||
|
||||
return `${redirectPath}#${anchor}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
--sf-split-sidebar: minmax(220px, 320px);
|
||||
--sf-product-card-min: 11rem;
|
||||
--sf-product-card-max: 22rem;
|
||||
--sf-sticky-header-scroll-padding: 9rem;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@@ -158,6 +159,10 @@
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-padding-top: var(--sf-sticky-header-scroll-padding);
|
||||
}
|
||||
|
||||
body.sf-body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
@@ -288,9 +293,13 @@ a:hover {
|
||||
}
|
||||
|
||||
.sf-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
margin-bottom: var(--sf-space-4);
|
||||
padding: var(--sf-space-3) 0;
|
||||
border-bottom: 1px solid var(--sf-border);
|
||||
background: var(--sf-bg);
|
||||
}
|
||||
|
||||
.sf-header__inner {
|
||||
@@ -371,6 +380,26 @@ a:hover {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sf-header__inner {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sf-header__brand-group {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sf-nav,
|
||||
.sf-rates {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.sf-crypto-icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
@@ -510,6 +539,76 @@ a:hover {
|
||||
border-color: var(--sf-warning-border);
|
||||
}
|
||||
|
||||
.sf-alert--toast {
|
||||
position: fixed;
|
||||
top: var(--sf-space-5);
|
||||
left: 50%;
|
||||
z-index: 20;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--sf-space-2);
|
||||
width: max-content;
|
||||
max-width: min(24rem, calc(100% - 2 * var(--sf-space-4)));
|
||||
margin: 0;
|
||||
padding: 0.6875rem 0.9375rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25;
|
||||
box-shadow: 0 2px 12px rgb(0 0 0 / 10%);
|
||||
pointer-events: none;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.sf-alert--toast.sf-alert--success {
|
||||
animation: sf-toast-dismiss 3s ease forwards;
|
||||
}
|
||||
|
||||
.sf-alert--toast.sf-alert--error {
|
||||
animation: sf-toast-dismiss 6s ease forwards;
|
||||
}
|
||||
|
||||
.sf-alert--toast::before {
|
||||
display: inline-flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: var(--sf-radius-full);
|
||||
color: var(--sf-on-accent);
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.sf-alert--toast.sf-alert--success::before {
|
||||
content: '✓';
|
||||
background: var(--sf-success);
|
||||
}
|
||||
|
||||
.sf-alert--toast.sf-alert--error::before {
|
||||
content: '×';
|
||||
background: var(--sf-error);
|
||||
}
|
||||
|
||||
@keyframes sf-toast-dismiss {
|
||||
0%,
|
||||
70% {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.sf-alert--toast {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
.sf-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -789,7 +888,7 @@ a:hover {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.sf-line-item__body > .sf-discount-error {
|
||||
.sf-line-item__body > .sf-cart-issue {
|
||||
margin: 0.125rem 0 0;
|
||||
}
|
||||
|
||||
@@ -866,8 +965,8 @@ a:hover {
|
||||
color: var(--sf-text-muted);
|
||||
}
|
||||
|
||||
.sf-discount-error {
|
||||
margin: 0 0 var(--sf-space-2);
|
||||
.sf-cart-issue {
|
||||
margin: var(--sf-space-2) 0;
|
||||
font-size: 0.85rem;
|
||||
color: var(--sf-error);
|
||||
line-height: 1.35;
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
{{> shop-nav}}
|
||||
{{> category-nav}}
|
||||
<main class='sf-main'>
|
||||
{{> feedback}}
|
||||
{{{body}}}
|
||||
</main>
|
||||
{{> shop-footer}}
|
||||
</div>
|
||||
{{> feedback}}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
{{@root.shopFiatCurrency}}</div>
|
||||
<div class='sf-line-item__note'>{{> product-delivery-note deliveryMode=deliveryMode}}</div>
|
||||
{{#if stockIssueMessage}}
|
||||
<p class='sf-discount-error'>{{stockIssueMessage}}</p>
|
||||
<p class='sf-cart-issue'>{{stockIssueMessage}}</p>
|
||||
{{else}}
|
||||
{{#unless stockForSession}}
|
||||
<p class='sf-text-subtle'>No more in stock beside your order</p>
|
||||
@@ -36,7 +36,7 @@
|
||||
{{/if}}
|
||||
{{#each @root.discounts}}
|
||||
{{#if (includes ineligibleVariantIds ../id)}}
|
||||
<p class='sf-discount-error'>Not eligible for code {{code}}</p>
|
||||
<p class='sf-cart-issue'>Not eligible for code {{code}}</p>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
<form class='sf-form-row' method='post' action='/shop/cart/product/update'>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
}}
|
||||
<span class='sf-discount-row__code sf-text-error'>{{code}}</span>
|
||||
</div>
|
||||
<p class='sf-discount-error'>{{issueMessage}}</p>
|
||||
<p class='sf-cart-issue'>{{issueMessage}}</p>
|
||||
{{else if amount}}
|
||||
<div class='sf-discount-row sf-discount-item'>
|
||||
{{> dismiss-button
|
||||
@@ -62,7 +62,7 @@
|
||||
</form>
|
||||
|
||||
{{#if cartTotalIssueMessage}}
|
||||
<p class='sf-discount-error'>{{cartTotalIssueMessage}}</p>
|
||||
<p class='sf-cart-issue'>{{cartTotalIssueMessage}}</p>
|
||||
{{/if}}
|
||||
|
||||
<div class='sf-stack sf-mt-4'>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{{#if feedback}}
|
||||
<p class='sf-alert sf-alert--{{feedback.type}}' role='status'>
|
||||
<p class='sf-alert sf-alert--{{feedback.type}} sf-alert--toast' role='status'>
|
||||
{{feedback.text}}
|
||||
</p>
|
||||
{{/if}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<li class='sf-card sf-product-card'>
|
||||
<li class='sf-card sf-product-card' id='product-card-{{selectedVariant.id}}'>
|
||||
{{#with selectedVariant}}
|
||||
{{#if thumbnailUrl}}
|
||||
<a class='sf-product-card__media-link' href='{{detailHref}}'>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class='sf-footer__row'>
|
||||
<div class='sf-footer__start'>
|
||||
{{#if simplexLink}}
|
||||
<a href='{{simplexLink}}' target='_blank' rel='noopener noreferrer'>Contact via Simplex</a>
|
||||
<a href='{{simplexLink}}' target='_blank' rel='noopener noreferrer'>Contact via SimpleX</a>
|
||||
{{/if}}
|
||||
<a href='https://nullcart.net/' target='_blank' rel='noopener noreferrer'>Powered by NullCart</a>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## 1. Requirements
|
||||
|
||||
- Ubuntu 22.04+ or similar Linux with Docker Engine and the Compose plugin — follow [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository). Tested and recommended on Ubuntu 22.04 LTS.
|
||||
- A domain name pointing at your server (A record for clearnet HTTPS)
|
||||
- A domain name pointing at your server (A records for apex and `www`)
|
||||
|
||||
## 2. Server setup
|
||||
|
||||
@@ -106,7 +106,7 @@ Remove the temporary bootstrap certificates under `deploy/certs/live/` (Certbot
|
||||
rm -rf deploy/certs/live/*
|
||||
```
|
||||
|
||||
Request the real certificate:
|
||||
Request the real certificate (apex + www):
|
||||
|
||||
```bash
|
||||
./deploy/scripts/issue-certs.sh --email you@example.com
|
||||
|
||||
@@ -9,9 +9,9 @@ usage() {
|
||||
cat <<EOF
|
||||
Usage: $(basename "$0") --email you@example.com
|
||||
|
||||
Obtain or renew Let's Encrypt certificates for CLEARNET_DOMAIN using the webroot
|
||||
challenge. Nginx must be running and serving /.well-known/acme-challenge/ from
|
||||
deploy/certbot/www.
|
||||
Obtain Let's Encrypt certificates for CLEARNET_DOMAIN and www.CLEARNET_DOMAIN using
|
||||
the webroot challenge. Nginx must be running and serving /.well-known/acme-challenge/
|
||||
from deploy/certbot/www.
|
||||
|
||||
Environment is read from .env.prod (CLEARNET_DOMAIN).
|
||||
EOF
|
||||
@@ -65,6 +65,7 @@ docker run --rm \
|
||||
--webroot \
|
||||
-w /var/www/certbot \
|
||||
-d "$CLEARNET_DOMAIN" \
|
||||
-d "www.${CLEARNET_DOMAIN}" \
|
||||
--email "$CERTBOT_EMAIL" \
|
||||
--agree-tos \
|
||||
--non-interactive
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${CLEARNET_DOMAIN};
|
||||
server_name ${CLEARNET_DOMAIN} www.${CLEARNET_DOMAIN};
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
root /var/www/certbot;
|
||||
@@ -13,7 +13,7 @@ server {
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name ${CLEARNET_DOMAIN};
|
||||
server_name ${CLEARNET_DOMAIN} www.${CLEARNET_DOMAIN};
|
||||
|
||||
ssl_certificate /etc/nginx/certs/live/${CLEARNET_DOMAIN}/fullchain.pem;
|
||||
ssl_certificate_key /etc/nginx/certs/live/${CLEARNET_DOMAIN}/privkey.pem;
|
||||
|
||||
Reference in New Issue
Block a user