This commit is contained in:
2025-10-02 08:18:37 +00:00
parent ea54638227
commit f81250a7c1
225 changed files with 76 additions and 23002 deletions

View File

@@ -1,151 +0,0 @@
kind: pipeline
type: docker
name: default
steps:
##############################
# Build and deploy docs
##############################
- name: build docs
image: node:18
pull: if-not-exists
environment:
FORCE_COLOR: "true"
commands:
- cd docs
- yarn install
- yarn docpress:build
when:
branch: [master]
event: [push]
- name: deploy docs
image: instrumentisto/rsync-ssh
pull: if-not-exists
environment:
RSYNC_HOST: ftp1.webmakers.de
RSYNC_PORT: 22222
RSYNC_USER: webmakers_tibi_docs_rsync_master
RSYNC_PASS:
from_secret: rsync_master
commands:
- cd docs
- >
rsync -rlcgD --perms -i -u -v --stats --progress
--delete
-e "sshpass -p $${RSYNC_PASS} ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p $${RSYNC_PORT}"
_docpress/
$${RSYNC_USER}@$${RSYNC_HOST}:./
when:
branch: [master]
event: [push]
##############################
# Demo project
##############################
- name: yarn install
image: node:18
pull: if-not-exists
environment:
FORCE_COLOR: "true"
commands:
- yarn install
- name: modify config
image: alpine/git
commands:
- sed -i 's#\(sentryEnvironment.*\)".*"#\1"${DRONE_BRANCH}"#g' frontend/src/config.ts
- sed -i 's#//\( sentry\\.init.*\)#\1#g' frontend/src/config.ts
- export $(cat .env | xargs)
- echo "PROJECT_RELEASE=$${RELEASE_PROJECT_SLUG}.r`git rev-list HEAD --count`-`git describe --all --long | sed 's+/+-+'`" >> .env
- export $(cat .env | xargs)
- cat .env
- sed -i 's#\(const release = \).*#\1"'$${PROJECT_RELEASE}'"#g' api/hooks/config-client.js
- name: build
image: node:18
commands:
- yarn build
- name: build ssr
image: node:18
commands:
- yarn build:server
- name: build legacy
image: node:18
commands:
- yarn build:legacy
- name: modify html
image: bash
commands:
- bash scripts/preload-meta.sh frontend/spa.html
- bash scripts/preload-meta.sh frontend/spa.html > frontend/_spa.html
- cp frontend/_spa.html frontend/spa.html
- export stamp=`date +%s`
- echo $$stamp
- sed -i s/__TIMESTAMP__/$$stamp/g frontend/spa.html
#- sed -i s/__TIMESTAMP__/$$stamp/g frontend/serviceworker.js
#- cat frontend/serviceworker.js
- cp frontend/spa.html api/templates/spa.html
- cat frontend/spa.html
# staging
- name: copy api config to staging
image: instrumentisto/rsync-ssh
volumes:
- name: data
path: /data
commands:
- rsync -av api /data/
- mkdir -p /data/frontend/dist
- rsync -av frontend/dist/ /data/frontend/dist/
when:
branch: [dev]
event: [push]
- name: review in staging
image: docker/compose:1.22.0
commands:
- docker-compose -p ${DRONE_BRANCH}-${DRONE_REPO_NAME}-${DRONE_REPO_OWNER} up -d --build --remove-orphans
volumes:
- name: docker
path: /var/run/docker.sock
when:
branch: [dev]
event: [push]
# live
- name: deploy master
image: instrumentisto/rsync-ssh
environment:
RSYNC_USER: ""
RSYNC_PASS:
from_secret: rsync_master
# remove if user and pass is set
failure: ignore
commands:
- apk add --no-cache sshpass curl
- scripts/deploy.sh ftp1.webmakers.de $${RSYNC_USER} $${RSYNC_PASS}
# - curl -X POST "https://www....de/api/ssr?token=TowendQhi&clear=1"
when:
branch: [master]
event: [push]
# - name: upload sourcemaps for glitchtip
# image: node
# environment:
# GLITCHTIP_TOKEN:
# from_secret: glitchtip_token
# commands:
# - yarn upload:sourcemaps
########
volumes:
- name: data
host:
path: /data/${DRONE_REPO_OWNER}/${DRONE_REPO_NAME}/${DRONE_BRANCH}
- name: docker
host:
path: /var/run/docker.sock

10
.env
View File

@@ -1,14 +1,14 @@
PROJECT_NAME=bkdf-tibi-2024 PROJECT_NAME=kontextwerk
TIBI_PREFIX=bkdf_tibi_2024 TIBI_PREFIX=kontextwerk
TIBI_NAMESPACE=bkdf_tibi_2024 TIBI_NAMESPACE=kontextwerk
CODER_UID=100 CODER_UID=100
CODER_GID=101 CODER_GID=101
START_SCRIPT=:ssr START_SCRIPT=:ssr
RSYNC_HOST=ftp1.webmakers.de RSYNC_HOST=ftp1.webmakers.de
RSYNC_PORT=22222 RSYNC_PORT=22222
RSYNC_USER=BinKrassDuFass_rsync_master RSYNC_USER=BinKrassDuFass_rsync_master
LIVE_URL=https://www.binkrassdufass.de LIVE_URL=https://www.kontextwerk.de
SENTRY_URL=https://sentry.basehosts.de SENTRY_URL=https://sentry.basehosts.de
SENTRY_ORG=webmakers SENTRY_ORG=webmakers
SENTRY_PROJECT=binkrassdufass-tibi-2024 SENTRY_PROJECT=kontextwerk-tibi-2024

126
README.md
View File

@@ -1,126 +0,0 @@
# Tibi Docs und Demo Projekt
Diese Repo enthält die Dokumentation zum TibiCMS und eine Demo-Projekt welches die Dokumentation begleitet und als Projetstarter fungiert.
## neues Projekt - Checkliste
- [x] neues Projekt im gitbase.de anlegen (cms/tibi_starter) als Vorlage verwenden
- [ ] klonen
- [ ] bereinigen
```sh
git filter-branch -f --index-filter 'git rm -rf --cached --ignore-unmatch .yarn/cache' HEAD
git filter-branch -f --index-filter 'git rm -rf --cached --ignore-unmatch docs' HEAD
git push --force
```
- [ ] anpassen
- `.env`
- `docker-compose-local.yml` -> `TIBI_API_NAMESPACE: & PROJECT_NAME`
- `.gitea/workflows/deploy.yaml` -> `name:`
- `./api/hooks/config.js` -> `name:`
- `api/...`
- [ ] upgraden
```sh
mkdir tmp
# evtl. zuvor: yarn install
make yarn-upgrade
make docker-pull
# falls Fehler auftreten, evtl. Berechtigungen fixen
make fix-permissions
```
- [ ] los programmieren
```sh
make docker-start
# bei erstem fehlerhaften Start, evtl. Berechtigungen fixen:
make fix-permissions
```
- [ ] Projekt in Tibi bekannt machen:
- <https://PROJEKTNAME-tibiadmin.code.testversion.online>
- Pfad der API-Konfig: `/data/api/config.yml`
- [ ] Website im Browser ansehen:
- <https://PROJEKTNAME.code.testversion.online>
- [ ] Testmails checken:
- <https://PROJEKTNAME-maildev.testversion.online>
- [ ] deploy
1. [ ] Subdomain im basispanel anlegen auf `../frontend/`
2. [ ] rsync-Account in basispanel anlegen auf `htdocs/`
3. [ ] Passwort in Secrets eintragen: <https://drone.gitbase.de>
4. [ ] `deploy.yaml` deploy rsync user anpassen
5. [ ] pushen
## SSR Aktivierung
Per se ist alles vorbereitet damit SSR aktiviert werden kann, hier muss nur START_SCRIPT=:ssr in der .env datei einkommentiert werden.
Es ist jedoch anzunehmen, dass während der entwicklung Dinge eingebaut wurden, dass die kompilierung schief geht. Um dies zu kontrollieren, muss man zunächst prüfen, ob Einträge in der SSR collection vorhanden sind. Gibt es keine Einträge, sollte man in die "/" Anfrage in den Netzwerkeinstellungen schauen. Hier ist in der Server Antwort im HTML template ein fehler zum debuggen zu finden!
# Git prozess - WICHTIG!
Der git Ablauf wurde mittels eines pre push Hooks modifiziert. Hier wird der aktuelle gepushte commit modifiziert, indem ein mongodump der aktuellen datenbank vor genommen wird und die kopie in ".gitea/actions/init-db/mongo-dump" abgespeichert wird.
Der pre-push hook liegt in .git und sieht wie folgt aus:
```sh
#!/bin/bash
ENV_FILE="$(git rev-parse --show-toplevel)/.env"
echo "PRE-PUSH HOOK: Running mongodump and adding it to the commit..."
# Check if the .env file exists
if [ ! -f "$ENV_FILE" ]; then
echo ".env file not found. Push cancelled."
exit 1
fi
# Source the .env file
source "$ENV_FILE"
# Name of the Docker container running MongoDB
CONTAINER_NAME="${PROJECT_NAME}-mongo-1"
# Directory to store the mongodump
OUTPUT_DIR=".gitea/actions/init-db/mongo-dump"
DUMP_DIR_NAME="tmp/mongo-dump"
# Message to append to the commit
APPEND_MSG="made a mongodump on current local database for tests to have data"
# Check if the container is running
if ! docker ps | grep -q "$CONTAINER_NAME"; then
echo "MongoDB container '$CONTAINER_NAME' is not running. Push cancelled."
exit 1
fi
# Perform mongodump
docker exec "$CONTAINER_NAME" mongodump --out /$DUMP_DIR_NAME && \
docker cp "$CONTAINER_NAME":/$DUMP_DIR_NAME "$OUTPUT_DIR"
# Check for successful mongodump
if [ $? -ne 0 ]; then
echo "Mongodump failed. Push cancelled. Make sure that the mongo container is up and running, necessary for the Workflow, because it needs a seeded database, wich needs a mongodump beforehand."
exit 1
fi
# Add the output to the current commit
git add "$OUTPUT_DIR"
# Amend the commit with a modified message
CURRENT_MSG=$(git log -1 --pretty=%B)
NEW_MSG="$CURRENT_MSG $APPEND_MSG"
git commit -m "$NEW_MSG"
# Continue with the push
exit 0
```
Diese Kopie wird gebraucht, um im Workflow unsere Lokale entwicklungsumgebung zu booten. Dieser workflow ist unter dem Namen lighthouse-evaluation in der deploy.yaml Datei zu finden. Es ist anzumerken, dass der Code funktioniert, er wurde in github getestet, jedoch in gitea das feature der Services noch nicht funktionsfähig ist, da Stand Februar 2024 das noch nicht unterstützt wird! Es wird jedoch sehr hilfreich sein, da
a) bei jedem Push eine lighthouse analyse durchgeführt wird und das Ergebnis davon in nextcloud hochgeladen wird. Bei bedarf kann natürlich auch die Funktion hinzugefügt werden, dass das Ergebnis direkt an seine Email gesandt wird oder ähnliches.
b) Man kann die Umgebung auch für Test zwecke verwenden. Wenn man also bspw. Cypress einbaut, so hat man direkt eine Umgebung um dies laufen zu lassen.

1
ai.md
View File

@@ -1 +0,0 @@
LMStudio?!

View File

@@ -1,253 +0,0 @@
name: selfImprovementChallenge
meta:
allowExportAll: true
label:
de: SelfImp. Challenge
en: SelfImp. Challenge
muiIcon: label
backup:
active: true
collectionName: backups
defaultSort:
field: name
order: ASC
views: &views
- type: table
columns:
- source: title
label:
de: Titel
en: Title
filter: true
- source: type
label:
de: Typ
en: Type
filter: true
- source: slug
label:
de: Slug
en: Slug
filter: true
tablist:
activeTab: generalDetails
tabs:
- name: generalDetails
label:
de: Allgemeine Details
en: General Details
subFields:
- source: activeAt
- source: type
- source: title
- source: images
- source: slug
- name: introduction
label:
de: Kurzbeschreibung
en: Short Description
subFields:
- source: introduction
- source: howItWorks
- source: blog
subNavigation:
- name: modalForeign
defaultSort:
field: name
order: ASC
views: *views
defaultCallback:
eval: |
(entry) => {
parent.selectEntry(entry)
}
permissions:
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
fields:
- name: slug
type: string
meta:
label:
de: Slug
en: Slug
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: activeAt
type: date
meta:
label:
de: Aktiv ab
en: Active at
widget: date
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: type
type: number
meta:
label:
de: Typ
en: Type
widget: select
choices:
- name: Krass Kraft
id: 1
- name: Crazy Calm
id: 2
- name: Crazy Crave Control
id: 3
- name: Krass Kreativ
id: 4
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: images
type: object
meta:
label:
de: Bilder
en: Images
subFields:
- name: preview
type: string
meta:
label:
de: Mobile
en: Mobile
widget: foreignKey
foreign:
collection: medialib
id: id
subNavigation: 0
render:
defaultCollectionViews: true
- name: detailed
type: string
meta:
label:
de: Desktop
en: Desktop
widget: foreignKey
foreign:
collection: medialib
id: id
subNavigation: 0
render:
defaultCollectionViews: true
- name: title
type: string
meta:
label:
de: Titel
en: Title
- name: introduction
type: string[]
meta:
label:
de: Kurzbeschreibung
en: Short Description
useDefaultArray: true
widget: richtext
- name: howItWorks
type: object
meta:
label:
de: Wie es funktioniert
en: How it works
subFields:
- name: invitation
type: string
meta:
label:
de: Einladung
en: Invitation
- !include fields/contentBlocks/stepNr.yml
- name: blog
type: object
meta:
label:
de: Blog
en: Blog
subFields:
- name: blogId
type: string
meta:
label:
de: Blog ID
en: Blog ID
widget: foreignKey
foreign:
collection: content
id: id
subNavigation: 0
render:
defaultCollectionViews: true
- name: thumbnail
type: string
meta:
label:
de: Thumbnail
en: Thumbnail
widget: foreignKey
foreign:
collection: medialib
id: id
subNavigation: 0
render:
defaultCollectionViews: true
- name: sources
type: object[]
meta:
label:
de: Quellen
en: Sources
subFields:
- name: source
type: string
meta:
label:
de: Quelle
en: Source
- name: url
type: string
meta:
label:
de: URL
en: URL

View File

@@ -1,360 +0,0 @@
name: orderReturnRequest
meta:
allowExportAll: true
label:
de: Retourenanfrage
en: Order Return Request
muiIcon: label
backup:
active: true
collectionName: backups
defaultSort:
field: insertTime
order: DESC
tablist:
activeTab: generalDetails
tabs:
- name: generalDetails
label:
de: Allgemeine Details
en: General Details
subFields:
- source: status
- source: notes
- name: customerDetails
label:
de: Kunden Details
en: Customer Details
subFields:
- source: email
- source: bigCommerceId
- name: products
label:
de: Produkte
en: Products
subFields:
- source: products
- name: returnShppingLabels
label:
de: Retourenlabels
en: Return Shipping Labels
subFields:
- source: returnShppingLabels
views: &views
- type: table
columns:
- source: status
filter: true
- source: email
filter: true
- source: bigCommerceId
filter:
type: foreignKey
- source: insertTime
filter: true
subNavigation:
- name: modalForeign
defaultSort:
field: "path"
order: "ASC"
views: *views
defaultCallback:
eval: |
//js
(entry) => {
parent.selectEntry(entry)
}
//!js
- name: new
label:
de: Neu
en: New
muiIcon: new
defaultSort:
field: insertTime
order: DESC
views: *views
filter:
status: pending
- name: inProgress
label:
de: In Bearbeitung
en: In Progress
muiIcon: inProgress
defaultSort:
field: insertTime
order: DESC
views: *views
filter:
status: approved
- name: done
label:
de: Abgeschlossen
en: Done
muiIcon: done
defaultSort:
field: insertTime
order: DESC
views: *views
filter:
status: refunded
- name: failed
label:
de: Fehlgeschlagen
en: Failed
muiIcon: error
defaultSort:
field: insertTime
order: DESC
views: *views
filter:
status: failed
- name: rejected
label:
de: Abgelehnt
en: Rejected
muiIcon: error
defaultSort:
field: insertTime
order: DESC
views: *views
filter:
status: rejected
permissions:
public:
methods:
get: true
post: true
put: false
delete: true
user:
methods:
get: true
post: true
put: true
delete: true
hooks:
get:
read:
type: javascript
file: hooks/orderReturnRequest/get_read.js
post:
create:
type: javascript
file: hooks/orderReturnRequest/post_create.js
return:
type: javascript
file: hooks/orderReturnRequest/post_return.js
put:
update:
type: javascript
file: hooks/orderReturnRequest/put_update.js
delete:
delete:
type: javascript
file: hooks/orderReturnRequest/delete_delete.js
return:
type: javascript
file: hooks/orderReturnRequest/delete_return.js
fields:
- name: status
type: string
meta:
label:
de: Status
en: Status
widget: select
choices:
- name: pending
id: pending
- name: approved
id: approved
- name: rejected
id: rejected
- name: refunded
id: refunded
- name: failed
id: failed
- name: email
type: string
meta:
label:
de: E-Mail
en: E-Mail
- name: bigCommerceId
type: number
meta:
label:
de: Bestellnummer
en: Order ID
widget: foreignKey
filter:
type: foreignKey
foreign:
collection: bigCommerceOrder
id: bigCommerceId
subNavigation: 0
render:
defaultCollectionViews: true
- name: returnShppingLabels
type: object[]
meta:
label:
de: Retourenlabels
en: Return Shipping Labels
subFields:
- name: cost
type: number
meta:
label:
de: Kosten
en: Cost
- name: label
type: file
meta:
label:
de: Label
en: Label
widget: file
- name: products
type: object[]
meta:
widget: containerLessObjectArray
label:
de: Produkte
en: Products
subFields:
- name: baseProductId
type: number
meta:
label:
de: Produkt ID
en: Product ID
widget: foreignKey
foreign:
collection: bigCommerceProduct
id: bigCommerceId
subNavigation: 0
render:
defaultCollectionViews: true
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: attachedImages
type: string[]
meta:
label:
de: Angehängte Bilder
en: Attached Images
widget: foreignKey
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
foreign:
collection: medialib
id: id
subNavigation: 0
render:
defaultCollectionViews: true
filter:
type: foreignKey
- name: quantity
type: number
meta:
label:
de: Menge
en: Quantity
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-4"
- name: productId
type: number
meta:
label:
de: Produkt ID in Bestellung
en: Product ID in Order
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-4"
- name: returnReason
type: string
meta:
label:
de: Retourengrund
en: Return Reason
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-4"
widget: select
choices:
- name: falsche Größe
id: wrongSize
- name: falsche Farbe
id: wrongColor
- name: falsches Produkt
id: wrongProduct
- name: beschädigt
id: damaged
- name: zu Groß
id: tooLarge
- name: zu Klein
id: tooSmall
- name: nicht Wie Beschrieben
id: notAsDescribed
- name: schlechte Qualität
id: poorQuality
- name: sieht Anders Aus
id: looksDifferent
- name: nicht Mehr Benötigt
id: noLongerNeeded
- name: schlechtes Preis Leistungs Verhältnis
id: poorValue
- name: andere
id: other
- name: notes
type: string
meta:
label:
de: Notizen
en: Notes
widget: textarea
containerProps:
layout:
size:
default: "col-12"
small: "col-12"
large: "col-12"

View File

@@ -1,183 +0,0 @@
name: orderRevokeRequest
meta:
allowExportAll: true
label:
de: Bestellung Abbruch
en: Revoke Order
muiIcon: label
backup:
active: true
collectionName: backups
defaultSort:
field: name
order: ASC
views: &views
- type: table
columns:
- source: status
filter: true
- source: email
filter: true
- source: bigCommerceId
filter:
type: foreignKey
- source: insertTime
filter: true
tablist:
activeTab: generalDetails
tabs:
- name: generalDetails
label:
de: Allgemeine Details
en: General Details
subFields:
- source: email
- source: bigCommerceId
- source: printfulId
- name: editable
label:
de: Bearbeitbar
en: Editable
subFields:
- source: status
- source: notes
subNavigation:
- name: modalForeign
defaultSort:
field: "path"
order: "ASC"
views: *views
defaultCallback:
eval: |
//js
(entry) => {
parent.selectEntry(entry)
}
//!js
- name: new
label:
de: Neu
en: New
muiIcon: new
defaultSort:
field: insertTime
order: DESC
views: *views
filter:
status: pending
- name: inProgress
label:
de: In Bearbeitung
en: In Progress
muiIcon: inProgress
defaultSort:
field: insertTime
order: DESC
views: *views
filter:
status: refunded
permissions:
public:
methods:
get: true
post: true
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
hooks:
get:
read:
type: javascript
file: hooks/orderRevokeRequest/get_read.js
post:
create:
type: javascript
file: hooks/orderRevokeRequest/post_create.js
return:
type: javascript
file: hooks/orderRevokeRequest/post_return.js
fields:
- name: status
type: string
meta:
label:
de: Status
en: Status
widget: select
choices:
- name:
de: Wartend
en: pending
id: pending
- name:
de: Zurückgezahlt
en: Refunded
id: refunded
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: email
type: string
meta:
label:
de: E-Mail
en: E-Mail
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: printfulId
type: number
meta:
label:
de: Printful Bestellungs-ID
en: Printful Order-ID
- name: bigCommerceId
type: number
meta:
label:
de: Bestellnummer
en: Order ID
widget: foreignKey
filter:
type: foreignKey
foreign:
collection: bigCommerceOrder
id: bigCommerceId
subNavigation: 0
render:
defaultCollectionViews: true
- name: notes
type: string
meta:
label:
de: Notizen
en: Notes
widget: string
inputProps:
multiline: true
containerProps:
layout:
size:
default: "col-12"
small: "col-12"
large: "col-12"

View File

@@ -1,68 +0,0 @@
name: qrCode
meta:
allowExportAll: true
label:
de: QR Codes
en: QR Codes
muiIcon: label
defaultSort:
field: name
order: ASC
backup:
active: true
collectionName: backups
views: &views
- type: table
columns:
- source: customer
filter:
type: foreignKey
- source: createdAt
filter: true
- source: id
renderValue:
raw: true
eval: |
(function(){
const id = $.id;
return `<button style="display: flex; align-items: center; padding: 8px 16px; background-color: #1D4ED8; color: white; font-weight: 600; border-radius: 6px; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); border: none; cursor: pointer; transition: background-color 0.3s ease;" onclick="event.stopPropagation(); event.preventDefault();navigator.clipboard.writeText('https://binkrassdufass.de/redirecttoprofile/${id}'); window.open('https://binkrassdufass.de/redirecttoprofile/${id}', '_blank')">Go to Profile <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-copy" style="margin-left: 8px;" viewBox="0 0 16 16"><path fill-rule="evenodd" d="M4 2a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2zm2-1a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V2a1 1 0 0 0-1-1zM2 5a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-1h1v1a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h1v1z"/></svg></button>` })()
permissions:
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
fields:
- name: customer
type: string
index: [single]
meta:
label:
de: Kunde
en: Customer
widget: foreignKey
foreign:
collection: bigCommerceCustomer
id: id
subNavigation: 0
render:
defaultCollectionViews: true
filter:
type: foreignKey
indexes:
- name: fulltext # Ein eindeutiger Name für den Index. Es ist optional, wird jedoch empfohlen, um den Index später leicht identifizieren zu können.
key: # Bestimmt, auf welche Felder der Index angewendet werden soll. Dies kann ein einfacher String sein, wenn der Index nur ein Feld umfasst, oder ein Array von Strings, wenn der Index mehrere Felder umfasst.
- $text:$**

View File

@@ -1,278 +0,0 @@
name: bigCommerceCustomer
# This is just so its easier to reference BigCommerce customer,
# its not intended to be used as a actual reference,
# just so its easier to reference customer in TibiCMS
meta:
label:
de: Kunde
en: Customer
views:
- type: table
columns:
- source: email
filter: true
- source: username
filter: true
- source: bigCommerceId
filter: true
- source: personalRecords.recording
filter:
type: foreignKey
tablist:
activeTab: generalDetails
tabs:
- name: generalDetails
label:
de: Allgemeine Details
en: General Details
subFields:
- source: email
- source: username
- source: bigCommerceId
- name: socialMedia
label:
de: Soziale Medien
en: Social Media
subFields:
- source: socialMediaAccounts
- name: personalRecords
label:
de: Persönliche Daten
en: Personal Records
subFields:
- source: personalRecords
- name: admin
label:
de: Admin
en: Admin
subFields:
- source: currentToken
- source: locked
backup:
active: true
collectionName: backups
subNavigation:
- name: modal
views:
- type: table
columns:
- source: email
defaultCallback: # Standard-Callback-Funktion, die ausgeführt wird, wenn keine andere spezifiziert ist.
eval: | # Der Code wird als JavaScript evaluiert.
//js
(entry) => { // Diese Funktion nimmt den Eintrag (entry) als Argument.
parent.selectEntry(entry) // Die Funktion selectEntry auf dem übergeordneten Objekt wird mit dem Eintrag als Argument aufgerufen.
}
//!js
hooks:
put:
# check and return jwt
update:
type: javascript
file: hooks/customer/put_update.js
get:
read:
type: javascript
file: hooks/customer/get_read.js
permissions:
public:
methods:
get: true
post: false
put: true
delete: false
user:
methods:
get: true
post: false
put: false
delete: false
token:${BIGCOMMERCE_WEBHOOK_TOKEN}:
methods:
get: true
post: true
put: true
delete: true
fields:
- name: bigCommerceId
type: number
meta:
label:
de: BigCommerce ID
en: BigCommerce ID
helperText:
de: Die ID des Kunden in BigCommerce
en: The ID of the customer in BigCommerce
- name: email
type: string
meta:
label:
de: Angehängte E-Mail
en: Attached Email
helperText:
de: Die E-Mail-Adresse des Kunden
en: The email address of the customer
- name: username
type: string
index: [single, unique]
meta:
label:
de: Benutzername
en: Username
helperText:
de: Der Benutzername des Kunden
en: The username of the customer
- name: locked
type: boolean
meta:
label:
de: Gesperrt
en: Locked
helperText:
de: Ob der Kunde gesperrt ist
en: Whether the customer is locked
- name: currentToken
type: string
meta:
label:
de: Aktueller Token
en: Current Token
inputProps:
disabled: true
helperText:
de: Der aktuelle Token des Kunden
en: The current token of the customer
- name: socialMediaAccounts
type: object
meta:
label:
de: Soziale Medien
en: Social Media
helperText:
de: Die sozialen Medien des Kunden
en: The social media of the customer
widget: containerLessObject
subFields:
- name: instagramLink
type: string
meta:
label:
de: Instagram Link
en: Instagram Link
helperText:
de: Der Instagram-Link des Kunden
en: The Instagram link of the customer
- name: facebookLink
type: string
meta:
label:
de: Facebook Link
en: Facebook Link
helperText:
de: Der Facebook-Link des Kunden
en: The Facebook link of the customer
- name: twitterLink
type: string
meta:
label:
de: Twitter Link
en: Twitter Link
helperText:
de: Der Twitter-Link des Kunden
en: The Twitter link of the customer
- name: tiktokLink
type: string
meta:
label:
de: TikTok Link
en: TikTok Link
helperText:
de: Der TikTok-Link des Kunden
en: The TikTok link of the customer
- name: youtubeLink
type: string
meta:
label:
de: YouTube Link
en: YouTube Link
helperText:
de: Der YouTube-Link des Kunden
en: The YouTube link of the customer
- name: personalRecords
type: object[]
meta:
label:
de: Persönliche Daten
en: Personal Records
helperText:
de: Die persönlichen Daten des Kunden
en: The personal records of the customer
subFields:
- name: title
type: string
meta:
label:
de: Titel
en: Title
- name: description
type: string
meta:
label:
de: Beschreibung
en: Description
- name: ageAtRecording
type: number
meta:
label:
de: Alter bei Aufnahme
en: Age at Recording
- name: priority
type: number
meta:
label:
de: Priorität
en: Priority
- name: recording
type: string
meta:
label:
de: Aufnahme
en: Recording
widget: foreignKey
filter:
type: foreignKey
foreign:
collection: medialib
subNavigation: 0
id: id
render:
defaultCollectionViews: true
- name: thumbnail
type: string
meta:
label:
de: Vorschaubild
en: Thumbnail
widget: foreignKey
foreign:
collection: medialib
subNavigation: 0
id: id
render:
defaultCollectionViews: true

View File

@@ -1,281 +0,0 @@
name: bigCommerceOrder
# This is just so its easier to reference BigCommerce order,
# its not intended to be used as a actual reference,
# just so its easier to reference order in TibiCMS
meta:
label:
de: Bestellung
en: Order
views: !include fieldLists/orderViews.yml
backup:
active: true
collectionName: backups
subNavigation:
- name: modal
views: !include fieldLists/orderViews.yml
defaultCallback: # Standard-Callback-Funktion, die ausgeführt wird, wenn keine andere spezifiziert ist.
eval: | # Der Code wird als JavaScript evaluiert.
//js
(entry) => { // Diese Funktion nimmt den Eintrag (entry) als Argument.
parent.selectEntry(entry) // Die Funktion selectEntry auf dem übergeordneten Objekt wird mit dem Eintrag als Argument aufgerufen.
}
//!js
permissions:
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: false
token:${BIGCOMMERCE_WEBHOOK_TOKEN}:
methods:
get: true
post: true
put: true
delete: true
hooks:
get:
read:
type: javascript
file: hooks/order/get_read.js
fields:
- name: status
type: string
meta:
label:
de: Status
en: Status
widget: select
choices:
- id: draft
name:
de: Entwurf
en: Draft
- id: pending
name:
de: Ausstehend
en: Pending
- id: failed
name:
de: Fehlgeschlagen
en: Failed
- id: completed
name:
de: Abgeschlossen
en: Completed
- id: cancelled
name:
de: Storniert
en: Cancelled
- id: inprocess
name:
de: In Bearbeitung
en: In Process
- id: onhold
name:
de: Zurückgehalten
en: On Hold
- id: partial
name:
de: Teilweise
en: Partial
- id: fulfilled
name:
de: Erfüllt
en: Fulfilled
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: statusSetAt
type: date
meta:
label:
de: Status gesetzt am
en: Status set at
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: bigCommerceId
type: number
meta:
label:
de: BigCommerce ID
en: BigCommerce ID
helperText:
de: Die ID der Bestellung in BigCommerce
en: The ID of the Order in BigCommerce
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: customerBigCommerceId
type: number
meta:
label:
de: Kunde BigCommerce ID
en: Customer BigCommerce ID
helperText:
de: Die ID des Kunden in BigCommerce
en: The ID of the customer in BigCommerce
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: customerTibiId
type: string
meta:
label:
de: Kunde
en: Customer
filter:
type: foreignKey
widget: foreignKey
foreign:
collection: bigCommerceCustomer
id: id
subNavigation: 0
render:
defaultCollectionViews: true
- name: products
type: object[]
meta:
label:
de: Produkte
en: Producte
widget: containerLessObjectArray
subFields:
- name: bigCommerceId
type: number
meta:
label:
de: BigCommerce ID
en: BigCommerce ID
helperText:
de: Die ID des Produkts in BigCommerce
en: The ID of the product in BigCommerce
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: tibiId
type: string
meta:
label:
de: Tibi ID
en: Tibi ID
widget: foreignKey
filter:
type: foreignKey
foreign:
collection: bigCommerceProduct
id: id
subNavigation: 0
render:
defaultCollectionViews: true
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: quantity
type: number
meta:
label:
de: Menge
en: Quantity
- name: shipments
type: object[]
meta:
label:
de: Lieferungen
en: Shipments
subFields:
- name: trackingUrl
type: string
meta:
label:
de: Tracking URL
en: Tracking URL
helperText:
de: Die URL, um das Paket zu verfolgen
en: The URL to track the package
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: trackingNumber
type: string
meta:
label:
de: Tracking Nummer
en: Tracking Number
helperText:
de: Die Tracking Nummer des Pakets
en: The tracking number of the package
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: carrier
type: string
meta:
label:
de: Carrier
en: Carrier
helperText:
de: Der Carrier des Pakets
en: The carrier of the package
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: sentAt
type: date
meta:
label:
de: Versendet am
en: Sent at
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"

View File

@@ -1,273 +0,0 @@
name: bigCommerceProduct
# This is just so its easier to reference BigCommerce products,
# its not intended to be used as a actual reference,
# just so its easier to reference products in TibiCMS
meta:
label:
de: Produkt
en: Product
views: &views
- type: cardList
mediaQuery: "(min-width: 1200px)"
selectionPriority: 1
fields:
- source: productName
filter: true
name:
de: Produktname
en: Product Name
- source: previewImage
filter: true
name:
de: Vorschaubild
en: Preview Image
- source: printfulProductId
filter: true
name:
de: Printful Produkt ID
en: Printful Product ID
- source: forcedWarning
filter: true
name:
de: Warnungsanzeige
en: Warning Display
- type: table
mediaQuery: "(min-width: 600px)"
columns:
- source: productName
- source: previewImage
- source: bigCommerceSKU
- source: printfulProductId
- source: forcedWarning
- type: simpleList
primaryText: previewImage
secondaryText: productName
tertiaryText: printfulProductId
tablist:
activeTab: generalDetails
tabs:
- name: generalDetails
label:
de: Allgemeine Details
en: General Details
subFields:
- source: productName
- source: previewImage
- source: bigCommerceSKU
- source: bigCommerceId
- name: edit
label:
de: Bearbeitbar
en: Editable
subFields:
- source: forcedWarning
- source: printfulProductId
- name: sizingChart
label:
de: Größentabelle
en: Sizing Chart
subFields:
- source: sizingChart
subNavigation:
- name: modal
views: *views
defaultCallback: # Standard-Callback-Funktion, die ausgeführt wird, wenn keine andere spezifiziert ist.
eval: | # Der Code wird als JavaScript evaluiert.
//js
(entry) => { // Diese Funktion nimmt den Eintrag (entry) als Argument.
parent.selectEntry(entry) // Die Funktion selectEntry auf dem übergeordneten Objekt wird mit dem Eintrag als Argument aufgerufen.
}
//!js
permissions:
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: false
delete: false
token:${BIGCOMMERCE_WEBHOOK_TOKEN}:
methods:
get: true
post: true
put: true
delete: true
hooks:
put:
update:
type: javascript
file: hooks/product/put_update.js
post:
create:
type: javascript
file: hooks/product/post_create.js
get:
read:
type: javascript
file: hooks/product/get_read.js
return:
type: javascript
file: hooks/product/get_return.js
fields:
- name: bigCommerceSKU
type: string
meta:
label:
de: BigCommerce SKU
en: BigCommerce SKU
helperText:
de: Die SKU des Produkts in BigCommerce
en: The SKU of the product in BigCommerce
- name: bigCommerceId
type: number
meta:
label:
de: BigCommerce ID
en: BigCommerce ID
helperText:
de: Die ID des Produkts in BigCommerce
en: The ID of the product in BigCommerce
- name: printfulProductId
type: string
meta:
label:
de: Printful Produkt ID
en: Printful Product ID
helperText:
de: Die ID des Produkts in Printful
en: The ID of the product in Printful
- name: previewImage
type: string
meta:
label:
de: Vorschaubild
en: Preview Image
widget: imageURL
- name: productName
type: string
meta:
label:
de: Produktname
en: Product Name
- name: forcedWarning
type: string
meta:
label:
de: Warnungsanzeige
en: Warning Display
- name: sizingChart
type: object
meta:
label:
de: Größentabelle
en: Sizing Chart
subFields:
- name: imageURL
type: string
meta:
label:
de: Bild
en: Image
widget: imageURL
- name: imageDescription
type: string
meta:
label:
de: Bildbeschreibung
en: Image Description
widget: richtext
- name: generalDescription
type: string
meta:
label:
de: Allgemeine Beschreibung
en: General Description
widget: richtext
- name: availableSizes
type: string[]
meta:
label:
de: Verfügbare Größen
en: Available Sizes
widget: select
choices:
- name: XS
id: XS
- name: S
id: S
- name: M
id: M
- name: L
id: L
- name: XL
id: XL
- name: 2XL
id: 2XL
- name: 3XL
id: 3XL
- name: 4XL
id: 4XL
- name: 5XL
id: 5XL
- name: 6XL
id: 6XL
- name: columns
type: object[]
meta:
label:
de: Spalten
en: Columns
subFields:
- name: label
type: string
meta:
label:
de: Label ID
en: Label ID
widget: foreignKey
foreign:
collection: module
id: label
subNavigation: 0
render:
defaultCollectionViews: true
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: sizes
type: string[]
meta:
label:
de: Größen
en: Sizes
useDefaultArray: true
widget: string
helperText:
de: In CM, selbe reihenfolge wie in der availableSizes
en: In CM, same order as in availableSizes

View File

@@ -1,38 +0,0 @@
# just here since bgicommerces graphql api regarding cart and checkout isnt working (only cart create is possible, otherwise it always says cart is not existant)
########################################################################
# cart
########################################################################
name: dummyCartEndpoint
meta:
hideInNavigation: true
openapi:
disabled: true
permissions:
public:
methods:
get: true
post: true
put: false
delete: false
user:
methods:
get: false
post: false
put: false
delete: false
hooks:
get:
read:
type: javascript
file: hooks/cart/get_read.js
post:
validate:
type: javascript
file: hooks/cart/post_validate.js
fields:

View File

@@ -1,74 +0,0 @@
- name: products
type: object[]
meta:
label:
de: Produkte
en: Products
subFields:
- name: productImage
type: string
meta:
label:
de: Produktbild
en: Product Image
widget: foreignKey
foreign:
collection: medialib
id: id
subNavigation: 0
render:
defaultCollectionViews: true
- name: productReference
type: string
meta:
label:
de: Produktreferenz
en: Product Reference
widget: foreignKey
foreign:
collection: bigCommerceProduct
id: id
subNavigation: 0
render:
defaultCollectionViews: true
- name: imageWidth
type: number
meta:
label:
de: Bildbreite
en: Image Width
helperText:
de: "Höhe des Bildes in Prozent."
en: "Height of the image in percent."
- name: imageHeight
type: number
meta:
label:
de: Bildhöhe
en: Image Height
helperText:
de: "Höhe des Bildes in Prozent."
en: "Height of the image in percent."
- name: imageTop
type: number
meta:
label:
de: Bildabstand oben
en: Image Top
helperText:
de: "Abstand des Bildes zum oberen Rand in Prozent."
en: "Distance of the image to the top edge in percent."
- name: imageLeft
type: number
meta:
label:
de: Bildabstand links
en: Image Left
helperText:
de: "Abstand des Bildes zum linken Rand in Prozent."
en: "Distance of the image to the left edge in percent."

View File

@@ -1,33 +0,0 @@
name: checkboxGroupInput
type: object
meta:
label:
de: Checkbox Gruppe
en: Checkbox Group
dependsOn:
eval: $parent?.inputWidgets?.includes('checkboxGroup')
helperText:
de: Ansammlung von Checkboxen
en: Collection of checkboxes
subFields:
- name: groupTitle
type: string
meta:
label:
de: Gruppe Titel
en: Group title
- name: checkboxes
type: object[]
meta:
label:
de: Checkbox Gruppe
en: Checkbox Group
direction: row
widget: containerLessObjectArray
subFields:
- name: standardInputProperties
meta:
widget: containerLessObject
subFields: !include standardInputProperties.yml

View File

@@ -1,17 +0,0 @@
name: dateInput
type: object
meta:
label:
de: Normaler Kalender
en: Normal calendar
helperText:
de: Datumsfeld
en: Date field
dependsOn:
eval: $parent?.inputWidgets?.includes('defaultCalendar')
subFields:
- name: standardInputProperties
type: object
meta:
widget: containerLessObject
subFields: !include standardInputProperties.yml

View File

@@ -1,46 +0,0 @@
name: datePickerInput
type: object
meta:
label:
de: Custom Kalender
en: Custom Calendar
dependsOn:
eval: $parent?.inputWidgets?.includes('customCalendar')
subFields:
- name: props
type: object
meta:
label:
de: Datumauswahl Eigenschaften
en: Date selection properties
subFields:
- name: allowedDateRanges
type: object[]
meta:
label:
de: Erlaubte Datumsbereiche
en: Allowed date ranges
widget: containerLessObjectArray
subFields:
- !include ../../fields/from.yml
- name: to
type: date
meta:
label:
de: Bis
en: To
widget: date
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- !include ../../fields/excludedDays.yml
- name: standardInputProperties
type: object
meta:
widget: containerLessObject
subFields: !include standardInputProperties.yml

View File

@@ -1,192 +0,0 @@
- name: emailSubject
type: string
meta:
label:
de: Email Betreff
en: Email Subject
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: emailReciever
type: string
meta:
label:
de: Email Empfänger
en: Email Reciever
helperText:
de: "Bsp: xyz@gmail.com"
en: "E.g.: xyz@gmail.com"
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- !include ../../fields/emailCC.yml
- name: emailIntroduction
type: string
meta:
label:
de: Email Einleitungssatz
en: Email Introduction Sentence
helperText:
de: "Bsp: Hallo xyz, sie haben eine neue Kaufanfrage erhalten!"
en: "E.g.: Hello xyz, you have received a new purchase request!"
- name: sendFormBtnText
type: string
meta:
label:
de: Formular Absenden Button Text
en: Form Submit Button Text
helperText:
de: "Bsp: Absenden"
en: "E.g.: Submit"
- name: rows
type: object[]
meta:
label:
de: Zeile
en: Row
widget: grid
addElementLabel: Zeile Hinzufügen
subFields:
- name: title
type: string
meta:
label:
de: Zeilenname
en: Row name
helperText:
de: "Sollte der Titel keinen Wert enthalten, wird kein Zeilenname angezeigt!"
en: "If the title does not contain a value, no row name will be displayed!"
- name: emailTitle
type: string
meta:
label:
de: Email Abschnitt Titel
en: Email section title
helperText:
de: "Sollte der Titel keinen Wert enthalten, wird kein Abschnitt Titel angezeigt!"
en: "If the title does not contain a value, no section title will be displayed!"
- name: columns
type: object[]
meta:
label:
de: Spalte
en: Column
addElementLabel:
de: Spalte hinzufügen
en: Add column
widget: grid
direction: horizontal
subFields:
- name: title
type: string
meta:
label:
de: Überschrift
en: Headline
helperText:
de: Optional
en: Optional
- name: emailTitle
type: string
meta:
label:
de: Email Abschnitt Titel
en: Email section title
- name: annotation
type: string
meta:
label:
de: Zusatzinformation
en: Additional information
- name: inputWidgets
type: string[]
meta:
label:
de: Angezeigte Eingabefelder
en: Displayed input fields
widget: selectArray
choices:
- name:
de: Nummernblock
en: Number block
id: labelNumber
- name:
de: Zeitenauswahlfeld
en: Time selection field
id: times
- name:
de: Auswahlfeld
en: Selection field
id: select
- name:
de: Datumsauswahl - Standard Kalender
en: Date selection - Standard calendar
id: defaultCalendar
- name:
de: Datumauswahl - Custom Kalender
en: Date selection - Custom calendar
id: customCalendar
- name:
de: Nummerfeld
en: Number field
id: number
- name:
de: Checkbox Gruppe
en: Checkbox group
id: checkboxGroup
- name:
de: Mehrfachauswahl
en: Multiple selection
id: multiSelect
- name:
de: Textfeld
en: Text field
id: text
- name:
de: Zeitenauswahl
en: Time selection
id: timeSelect
- name:
de: Standardauswahl
en: Standard selection
id: standardSelect
- !include standardSelect.yml
- !include labelNumberInput.yml
- !include timesInput.yml
- !include dateInput.yml
- !include numberInput.yml
- !include checkboxGroup.yml
- !include datePicker.yml
- !include multiSelectInput.yml
- !include textInputs.yml
- !include timeSelect.yml

View File

@@ -1,79 +0,0 @@
name: labelNumberInput
type: object[]
meta:
label:
de: Nummer block
en: Number block
dependsOn:
eval: $parent?.inputWidgets?.includes('labelNumber')
helperText:
de: Links beschreibender Text, rechts zahleneingabe
en: Left descriptive text, right number input
subFields:
- name: group
type: number
meta:
label:
de: Gruppe
en: Group
helpterText:
de: Pflichtfeld seperierung. Aus einem Nummernblock muss mindestens eine Gruppe input haben.
en: Mandatory field separation. A number block must have at least one group input.
- name: title
type: string
meta:
label: Titel
helperText:
de: Block Titel
en: Block title
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: emailName
type: string
meta:
label:
de: Email Name
en: Email name
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: block
type: object[]
meta:
label: Block
widget: containerLessObjectArray
subFields:
- name: label
type: string
meta:
label: Label
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: emailName
type: string
meta:
label:
de: Email Name
en: Email name
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"

View File

@@ -1,50 +0,0 @@
name: multiSelectInput
type: object
meta:
label:
de: Mehrfachauswahl Input
en: Multi Select Input
dependsOn:
eval: $parent?.inputWidgets?.includes('multiSelect')
subFields:
- name: standardInputProperties
type: object
meta:
widget: containerLessObject
subFields: !include standardInputProperties.yml
- name: options
type: object[]
meta:
label:
de: Mehrfachauswahl Optionen
en: Multi Select Options
direction: row
widget: containerLessObjectArray
subFields:
- name: name
type: string
meta:
label:
de: Name
en: Name
- name: props
type: object
meta:
label:
de: Mehrfachauswahl Eigenschaften
en: Multi Select Properties
subFields:
- name: additionalAddableValues
type: boolean
meta:
label:
de: Zusätzliche hinzufügbare Werte
en: Additional addable values
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"

View File

@@ -1,14 +0,0 @@
name: numberInput
type: object
meta:
label:
de: Nummereingabe
en: Number Input
dependsOn:
eval: $parent?.inputWidgets?.includes('number')
subFields:
- name: standardInputProperties
type: object
meta:
widget: containerLessObject
subFields: !include standardInputProperties.yml

View File

@@ -1,71 +0,0 @@
- name: emailTitle
type: string
meta:
label:
de: Email Titel
en: Email title
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: placeholder
type: string
meta:
label:
de: Platzhalter
en: Placeholder
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: notRequired
type: boolean
meta:
label:
de: nicht Notwendig
en: not required
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: fieldOrder
type: number
meta:
label:
de: Reihenfolge
en: Order
helperText:
de: Die kleinste angegebene Zahl wird am weitesten oben in der Formularspalte stehen
en: The smallest specified number will be at the top of the form column.
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: textTitle
type: string
meta:
label:
de: Text Titel
en: Text title
helperText:
de: Alternative zu textPlaceholder, steht dann über dem Inputfeld
en: Alternative to textPlaceholder, then stands above the input field
- name: groupTitle
type: string
meta:
label:
de: Gruppe Titel
en: Group title

View File

@@ -1,58 +0,0 @@
name: standardSelect
type: object
meta:
label: Standardauswahl
dependsOn:
eval: $parent?.inputWidgets?.includes('standardSelect')
subFields:
- name: standardInputProperties
type: object
meta:
widget: containerLessObject
subFields: !include standardInputProperties.yml
- name: selectEntries
type: object[]
meta:
label:
de: Auswahleingabe Möglichkeiten
en: Selection input options
widget: containerLessObjectArray
addElementLabel: Auswahleingabe hinzufügen
direction: horizontal
subFields:
- name: shownValue
type: string
meta:
label:
de: Angezeigter Wert
en: Displayed value
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: value
type: string
meta:
label:
de: per Email gesendeter Wert
en: Value sent by email
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: defaultValue
type: boolean
meta:
label:
de: Standardwert
en: Default value
helperText:
de: "Wird dieser Wert ausgewählt, wird er als Standardwert gesetzt"
en: "If this value is selected, it will be set as the default value"

View File

@@ -1,52 +0,0 @@
name: textInput
type: object[]
meta:
label: Textfeld
addElementLabel: Textfeld hinzufügen
dependsOn:
eval: $parent?.inputWidgets?.includes('text')
subFields:
- name: standardInputProperties
type: object
meta:
widget: containerLessObject
subFields: !include standardInputProperties.yml
- name: textArea
type: boolean
meta:
label:
de: Großes Textfeld
en: Large text field
containerProps:
layout:
size:
default: "col-4"
small: "col-6"
large: "col-4"
- name: emailValidation
type: boolean
meta:
label:
de: E-Mail-Validierung
en: E-mail validation
containerProps:
layout:
size:
default: "col-4"
small: "col-6"
large: "col-4"
- name: telValidation
type: boolean
meta:
label:
de: Telefon-Validierung
en: Phone validation
containerProps:
layout:
size:
default: "col-4"
small: "col-6"
large: "col-4"

View File

@@ -1,45 +0,0 @@
name: timeSelect
type: object
meta:
label: Zeitenwahl
dependsOn:
eval: $parent?.inputWidgets?.includes('timeSelect')
subFields:
- name: standardInputProperties
type: object
meta:
widget: containerLessObject
subFields: !include standardInputProperties.yml
- name: selectEntries
type: object[]
meta:
label: Auswahleingabe
widget: containerLessObjectArray
helperText: "Die Angaben werden in folgendes Übersetzt: Anfangspunkt - Endpunkt"
direction: horizontal
subFields:
- name: leftSide
type: string
meta:
label: Anfangspunkt
helperText: Bspw. 14:30
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: rightSide
type: string
meta:
label: Endpunkt
helperText: Bspw. 15:30
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"

View File

@@ -1,60 +0,0 @@
name: timesInput
type: object
meta:
label:
de: Zeitenauswahlfeld
en: Time input
dependsOn:
eval: $parent?.inputWidgets?.includes('times')
helperText:
de: Selectfeld mit von - bis Angabe
en: Select field with from - to specification
subFields:
- name: times
type: object[]
meta:
label:
de: Zeitenauswahl Möglichkeiten
en: Time selection options
helperText:
de: "Die Angaben werden in folgendes Übersetzt: Anfangspunkt - Endpunkt"
en: "The information is translated into the following: starting point - end point"
direction: horizontal
widget: containerLessObjectArray
subFields:
- name: from
type: string
meta:
label:
de: Anfangspunkt
en: Starting point
helperText:
de: Bspw. 14:30
en: E.g. 14:30
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: to
type: string
meta:
label: Endpunkt
helperText:
de: Bspw. 15:30
en: E.g. 15:30
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: standardInputProperties
type: object
meta:
widget: containerLessObject
subFields: !include standardInputProperties.yml

View File

@@ -1,12 +0,0 @@
- type: table
columns:
- source: status
filter: true
- source: customerTibiId
filter:
type: foreignKey
- source: insertTime
filter: true
- source: products.tibiId
filter:
type: foreignKey

View File

@@ -1,143 +0,0 @@
name: helpCenterChapter
# Metaangaben zur Kollektion welche in der Admin-UI verwendet werden können
meta:
openapi:
disabled: true
# Navigationseintrag in der Admin-UI
label:
de: Helpcenter Kapitel
en: Helpcenter Chapter
backup:
active: true
collectionName: backups
# Icon (Material UI) für den Navigationseintrag
muiIcon: web
tablist:
activeTab: general
tabs:
- name: general
label:
de: Allgemein
en: General
subFields:
- source: title
- source: slug
- source: brightIcon
- source: darkIcon
- name: questions
label:
de: Fragen
en: Questions
subFields:
- source: questions
views:
# Desktop
- type: table
mediaQuery: "(min-width:600px)"
columns:
- source: title
name: Titel
filter: true
- source: slug
name: Slug
filter: true
permissions:
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
# Feldliste der Kollektion
fields:
- name: title
type: string
meta:
label:
de: Titel
en: Title
- name: slug
type: string
meta:
label:
de: Slug
en: Slug
- name: brightIcon
type: string
meta:
label:
de: Icon Hell
en: Icon Bright
widget: foreignKey
foreign:
collection: medialib
id: id
subNavigation: 0
render:
defaultCollectionViews: true
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: darkIcon
type: string
meta:
label:
de: Icon Dunkel
en: Icon Dark
widget: foreignKey
foreign:
collection: medialib
id: id
subNavigation: 0
render:
defaultCollectionViews: true
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: questions
type: object[]
meta:
label:
de: Fragen
en: Questions
widget: containerLessObjectArray
subFields:
- name: priority
type: boolean
meta:
label:
de: Priorität
en: Priority
helperText:
de: ob sie direkt ganz oben angezeigt werden soll
en: if it should be displayed directly at the top
- name: page
type: string
meta:
label:
de: Seite
en: Page
widget: foreignKey
foreign:
collection: content
id: path
subNavigation: 0
render:
defaultCollectionViews: true

View File

@@ -1,61 +0,0 @@
# no data in collection, only for post hook
name: login
uploadPath: ../media/login
# Metaangaben zur Kollektion welche in der Admin-UI verwendet werden können
meta:
openapi:
disabled: true
hideInNavigation: true
# Navigationseintrag in der Admin-UI
label: { de: "Login", en: "Login" }
# Icon (Material UI) für den Navigationseintrag
muiIcon: web
# Identifizierung eines Eintrags für z.B. Select-Boxen in der Admin-UI
rowIdentTpl: { twig: "{{ id }}" }
# Standardsortierung der Liste
defaultSort: { field: "id", order: "ASC" }
# Admin-Backend Ansichten
defaultImageFilter: s
views:
# Mobile Darstellung
- type: simpleList
mediaQuery: "(max-width:599px)"
primaryText: id
# Desktop
- type: table
mediaQuery: "(min-width:600px)"
columns:
- id
# Zugriff auf diese Kollektion
permissions:
# öffentlicher Zugriff
public:
methods:
# Liste und Einzeleinträge lesen
get: false
# neuen Eintrag anlegen
post: true
# Eintrag editieren
put: false
# Eintrag löschen
delete: false
# zum Projekt zugeordneter Benutzer ohne Zusatzberechtigungen
user:
methods:
get: true
post: true
put: true
delete: true
hooks:
post:
# check and return jwt
create:
type: javascript
file: hooks/login/post_create.js
# Feldliste der Kollektion
fields: []

View File

@@ -1,24 +0,0 @@
name: lookCombination
meta:
allowExportAll: true
label:
de: Styling Kombinationen
en: Look Combination
muiIcon: label
views: []
permissions:
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
fields: []

View File

@@ -1,75 +0,0 @@
name: productBackgroundImage
meta:
allowExportAll: true
label:
de: Produkt Bg.
en: Product Bg.
muiIcon: label
defaultSort:
field: name
order: ASC
views: &views
- type: table
columns:
- source: type
filter: true
- source: image
filter:
type: foreignKey
permissions:
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
fields:
- name: type
type: string
meta:
label:
de: Typ
en: Type
widget: select
choices:
- id: standard
label:
de: Standard
en: Standard
- id: dark
label:
de: Dunkel
en: Dark
- name: image
type: string
meta:
label:
de: Tibi ID
en: Tibi ID
widget: foreignKey
foreign:
collection: medialib
id: id
subNavigation: 0
render:
defaultCollectionViews: true
filter:
type: foreignKey
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"

View File

@@ -1,54 +0,0 @@
name: productBenefit
meta:
allowExportAll: true
label:
de: Product Vorteile
en: Product Benefits
muiIcon: label
defaultSort:
field: name
order: ASC
views: &views
- type: table
columns:
- source: title
label:
de: Titel
en: Title
- source: description
label:
de: Beschreibung
en: Description
permissions:
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
fields:
- name: title
type: string
meta:
label:
de: Titel
en: Title
- name: description
type: string
meta:
widget: richtext
label:
de: Beschreibung
en: Description

View File

@@ -1,301 +0,0 @@
name: rating
meta:
label: { de: "Bewertungen", en: "Ratings" }
muiIcon: reviews
views: &views
- type: simpleList
mediaQuery: "max-width: 600px"
- type: table
columns:
- source: bigCommerceProductId
filter:
type: foreignKey
- source: bigcommerceOrderId
filter:
type: foreignKey
- source: rating.overall
filter: true
- source: status
filter: true
tablist:
activeTab: generalDetails
tabs:
- name: generalDetails
label:
de: Allgemeine Details
en: General Details
subFields:
- source: bigCommerceProductId
- source: bigcommerceOrderId
- source: status
- source: review_date
- source: bigcommerceReviewId
- name: rating
label:
de: Bewertung
en: Rating
subFields:
- source: rating
- source: comment
- source: title
subNavigation:
- name: modal
views: *views
defaultCallback: # Standard-Callback-Funktion, die ausgeführt wird, wenn keine andere spezifiziert ist.
eval: | # Der Code wird als JavaScript evaluiert.
//js
(entry) => { // Diese Funktion nimmt den Eintrag (entry) als Argument.
parent.selectEntry(entry) // Die Funktion selectEntry auf dem übergeordneten Objekt wird mit dem Eintrag als Argument aufgerufen.
}
//!js
permissions:
public:
methods:
get: true
post: true
put: true
delete: false
user:
methods:
get: true
post: true
put: true
delete: false
hooks:
delete:
delete:
type: javascript
file: hooks/rating/delete_delete.js
return:
type: javascript
file: hooks/rating/delete_return.js
get:
read:
type: javascript
file: hooks/rating/get_read.js
post:
validate:
type: javascript
file: hooks/rating/post_validate.js
create:
type: javascript
file: hooks/rating/post_create.js
return:
file: hooks/rating/post_return.js
type: javascript
put:
validate:
type: javascript
file: hooks/rating/put_validate.js
update:
type: javascript
file: hooks/rating/put_update.js
return:
file: hooks/rating/put_return.js
type: javascript
fields:
- name: bigcommerceOrderId
type: number
index: [single]
meta:
filter:
type: foreignKey
label: { de: "Interne Bestellnummer", en: "Internal Order Id" }
widget: foreignKey
foreign:
collection: bigCommerceOrder
subNavigation: 0
id: bigCommerceId
render:
defaultCollectionViews: true
validator:
required: true
- name: bigCommerceProductId
index: [single]
type: number
meta:
filter:
type: foreignKey
label: { de: "Produkt", en: "Product" }
widget: foreignKey
foreign:
collection: bigCommerceProduct
subNavigation: 0
id: bigCommerceId
render:
defaultCollectionViews: true
validator:
required: true
- name: rating
index: [single]
type: object
meta:
widget: containerLessObject
label: { de: "Bewertung", en: "Rating" }
subFields:
- name: quality
type: number
meta:
label: { de: "Qualität", en: "Quality" }
helperText: { de: "1 - Schlecht; 5 - Sehr Gut", en: "1 - Bad; 5 - Very Good" }
validator:
eval: |
(function () {
return $this >= 0 && $this <= 5;
})()
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-4"
- name: priceQualityRatio
type: number
meta:
label: { de: "Preis-Leistungs-Verhältnis", en: "Price-Quality Ratio" }
helperText: { de: "1 - Schlecht; 5 - Sehr Gut", en: "1 - Bad; 5 - Very Good" }
validator:
eval: |
(function () {
return $this >= 0 && $this <= 5;
})()
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-4"
- name: comfort
type: number
meta:
label: { de: "Komfort", en: "Comfort" }
helperText: { de: "1 - Unbequem; 5 - Sehr Bequem", en: "1 - Uncomfortable; 5 - Very Comfortable" }
validator:
eval: |
(function () {
return $this >= 0 && $this <= 5;
})()
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-4"
- name: overall
type: number
meta:
label: { de: "Gesamt", en: "Overall" }
helperText: { de: "1 - Schlecht; 5 - Sehr Gut", en: "1 - Bad; 5 - Very Good" }
validator:
eval: |
(function () {
return $this >= 0 && $this <= 5;
})()
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-4"
- name: comment
type: string
meta:
label: { de: "Kommentar", en: "Comment" }
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
inputProps:
multiline: true
- name: title
type: string
meta:
label: { de: "Titel", en: "title" }
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
inputProps:
multiline: true
- name: review_date
type: date
validator:
eval: |
(function () {
return (new Date($this) !== "Invalid Date") && !isNaN(new Date($this));
})()
meta:
label: { de: "Erstellungsdatum", en: "Creation date" }
defaultValue:
eval: new Date()
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: status
type: string
index: [single]
validator:
eval: (["pending", "approved", "rejected"].includes($this))
meta:
label: Status
widget: "select"
defaultValue: pending
choices:
- name: { de: wartend, en: pending }
id: pending
- name: { de: bestätigt, en: approved }
id: approved
- name: { de: abgelehnt, en: rejected }
id: rejected
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: bigcommerceReviewId
type: string
meta:
label:
de: "BigCommerce Review Id"
en: "BigCommerce Review Id"
helperText:
de: "Die ID der Bewertung in BigCommerce"
en: "The ID of the review in BigCommerce"
indexes:
- name: fulltext # Ein eindeutiger Name für den Index. Es ist optional, wird jedoch empfohlen, um den Index später leicht identifizieren zu können.
key: # Bestimmt, auf welche Felder der Index angewendet werden soll. Dies kann ein einfacher String sein, wenn der Index nur ein Feld umfasst, oder ein Array von Strings, wenn der Index mehrere Felder umfasst.
- $text:$**

View File

@@ -1,196 +0,0 @@
name: selfImprovementChapter
meta:
allowExportAll: true
label:
de: SelfImp. Kapitel
en: SelfImp. Chapter
muiIcon: label
backup:
active: true
collectionName: backups
defaultSort:
field: name
order: ASC
views: &views
- type: table
columns:
- source: title
label:
de: Titel
en: Title
filter: true
- source: shortDescription
label:
de: Kurzbeschreibung
en: Short Description
filter: true
tabs:
activeTab: generalDetails
tabs:
- name: generalDetails
label:
de: Allgemeine Details
en: General Details
subFields:
- source: title
- source: alias
- source: previewVideo
- source: previewImage
- source: locked
- name: homepage
label:
de: Homepage
en: Homepage
subFields:
- source: shortDescription
- source: description
subNavigation:
- name: modalForeign
defaultSort:
field: name
order: ASC
views: *views
defaultCallback:
eval: |
(entry) => {
parent.selectEntry(entry)
}
permissions:
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
fields:
- name: type
type: number
meta:
label:
de: Typ
en: Type
widget: select
choices:
- name: Krass Kraft
id: 1
- name: Crazy Calm
id: 2
- name: Crazy Crave Control
id: 3
- name: Krass Kreativ
id: 4
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: title
type: string
meta:
label:
de: Titel
en: Title
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: alias
type: string
meta:
label:
de: Alias
en: Alias
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: shortDescription
type: string
meta:
label:
de: Kurze Beschreibung
en: Short Description
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: description
type: string
meta:
widget: richtext
label:
de: Beschreibung
en: Description
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"
- name: previewVideo
type: string
meta:
label:
de: Vorschau Video
en: Preview Video
widget: foreignKey
foreign:
collection: medialib
id: id
subNavigation: 0
render:
defaultCollectionViews: true
- name: previewImage
type: string
meta:
label:
de: Vorschau Bild
en: Preview Image
widget: foreignKey
foreign:
collection: medialib
id: id
subNavigation: 0
render:
defaultCollectionViews: true
- name: locked
type: boolean
meta:
label:
de: Gesperrt
en: Locked
helperText:
de: Wenn gesperrt, kann das Kapitel nicht geöffnet werden.
en: If locked, the chapter cannot be opened.
containerProps:
layout:
size:
default: "col-6"
small: "col-12"
large: "col-6"

View File

@@ -1,74 +0,0 @@
name: shopStatus
# Metaangaben zur Kollektion welche in der Admin-UI verwendet werden können
meta:
openapi:
disabled: true
# Navigationseintrag in der Admin-UI
backup:
active: true
collectionName: backups
label:
de: Shop Status
en: Shop Status
# Icon (Material UI) für den Navigationseintrag
muiIcon: web
views:
# Mobile Darstellung
- type: simpleList
mediaQuery: "(max-width:599px)"
primaryText: status
secondaryText: password
# Desktop
- type: table
mediaQuery: "(min-width:600px)"
columns:
- source: status
label:
de: Status
en: Status
filter: true
- source: password
label:
de: Passwort
en: Password
filter: true
permissions:
# öffentlicher Zugriff
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
# Feldliste der Kollektion
fields:
- name: status
type: string
meta:
label:
de: Status
en: Status
widget: select
choices:
- name: open
id: open
- name: login
id: login
- name: password
type: string
meta:
label:
de: Passwort
en: Password

View File

@@ -1,24 +0,0 @@
name: sizingDetails
meta:
allowExportAll: true
label:
de: Größen Details
en: Sizing Details
muiIcon: label
views: []
permissions:
public:
methods:
get: true
post: false
put: false
delete: false
user:
methods:
get: true
post: true
put: true
delete: true
fields: []

View File

@@ -1,92 +0,0 @@
########################################################################
# Webhooks
########################################################################
name: webhook
meta:
openapi:
disabled: true
label: { de: "Webhooks", en: "webhooks" }
muiIcon: http
rowIdentTpl: { twig: "{{ insertTime }} - {{ type }}" }
views:
- type: simpleList
mediaQuery: "(max-width: 600px)"
primaryText: type
secondaryText: scope
- type: table
columns:
- type
- scope
- active
permissions:
public:
methods:
get: false
post: true
put: false
delete: false
user:
methods:
get: true
post: true
put: false
delete: false
"token:${BIGCOMMERCE_WEBHOOK_TOKEN}":
methods:
get: true
post: true
put: false
delete: false
hooks:
post:
create:
type: javascript
file: hooks/webhook/post_create.js
put:
update:
type: javascript
file: hooks/webhook/put_update.js
delete:
delete:
type: javascript
file: hooks/webhook/delete_delete.js
fields:
- name: type
type: string
meta:
label: { de: "Typ", en: "Type" }
widget: select
choices:
- id: "bigCommerce"
name: "BigCommerce"
- id: "printful"
name: "Printful"
- name: scope
type: string
meta:
label: { de: "Scope", en: "Scope" }
helperText:
de: "The scope of the webhook, for example: store/product/*"
en: "The scope of the webhook, for example: store/product/*"
- name: active
type: boolean
meta:
label: { de: "Aktiv", en: "Active" }
helperText:
de: "Aktiviert oder deaktiviert den Webhook"
en: "Enables or disables the webhook"
- name: webhookId
type: number
meta:
label: { de: "Webhook ID", en: "Webhook ID" }
helperText:
de: "Die ID des Webhooks"
en: "The ID of the webhook"
inputProperties:
disabled: true

View File

@@ -1,36 +0,0 @@
########################################################################
# cart
########################################################################
name: dummyWishlistEntryEndpoint
meta:
hideInNavigation: true
openapi:
disabled: true
permissions:
public:
methods:
get: true
post: true
put: false
delete: false
user:
methods:
get: false
post: false
put: false
delete: false
hooks:
get:
read:
type: javascript
file: hooks/wishlist/get_read.js
post:
validate:
type: javascript
file: hooks/wishlist/post_create.js
fields:

View File

@@ -8,48 +8,29 @@ meta:
dashboard: !include helper/dashboard.yml dashboard: !include helper/dashboard.yml
injectIntoHead: injectIntoHead:
# inject font faces (not possible in shadow dom for preview)
- eval: | - eval: |
"<link rel='stylesheet' href='" + $projectBase + "_/assets/variables/variables.css'>" "<link rel='stylesheet' href='" + $projectBase + "_/assets/variables/variables.css'>"
- eval: | - eval: |
"<link rel='stylesheet' href='" + $projectBase + "_/assets/fonts/fonts.css'>" "<link rel='stylesheet' href='" + $projectBase + "_/assets/fonts/fonts.css'>"
# Liste möglicher Berechtigungen, die Benutzern zugeordnet werden können
permissions: permissions:
- name: pages - name: pages
label: label:
de: Seiten de: Seiten
en: Pages en: Pages
collections: collections:
- !include collections/bigCommerceOrder.yml
- !include collections/OrderReturnRequest.yml
- !include collections/OrderRevokeRequest.yml
- !include collections/contact.yml - !include collections/contact.yml
- !include collections/rating.yml
- !include collections/bigCommerceProduct.yml
- !include collections/bigCommerceCustomer.yml
- !include collections/QRCode.yml
- !include collections/productBenefit.yml
- !include collections/banner.yml - !include collections/banner.yml
- !include collections/content.yml - !include collections/content.yml
- !include collections/selfImprovementChapter.yml
- !include collections/Challenge.yml
- !include collections/medialib.yml - !include collections/medialib.yml
- !include collections/navigation.yml - !include collections/navigation.yml
- !include collections/sizingDetails.yml
- !include collections/lookCombination.yml
- !include collections/tag.yml - !include collections/tag.yml
- !include collections/webhook.yml - !include collections/webhook.yml
- !include collections/login.yml
- !include collections/action.yml - !include collections/action.yml
- !include collections/wishlistEntry.yml
- !include collections/module.yml - !include collections/module.yml
- !include collections/helpcenterChapter.yml
- !include collections/shopStatus.yml
- !include collections/productBackgroundImage.yml
- !include collections/ssr.yml - !include collections/ssr.yml
- !include collections/lighthouseSubpath.yml - !include collections/lighthouseSubpath.yml
- !include collections/lighthouse.yml - !include collections/lighthouse.yml
- !include collections/dummyCartEndpoint.yml
- !include collections/backups.yml - !include collections/backups.yml
jobs: jobs:

View File

@@ -1,46 +0,0 @@
const { getCart, getRedirectUrl } = require("../lib/bigcommerceRestAPI")
const { bigcommerceStoreHash, bigcommerceBaseURL, bigCommerceCliendId, bigCommerceClientSecret } = require("../config")
const { getCustomerById, getLoginUrl } = require("../lib/bigcommerceRestAPI")
const { withAccount } = require("../lib/utils")
const { mapRestApiCartToGraphQL } = require("./helper")
;(function () {
if (context.user.auth()) return {}
const checkout = context.request().query("checkout")
const loggedInCheckout = context.request().query("loggedInCheckout")
if (checkout) {
if (loggedInCheckout) {
withAccount((loginClaims) => {
const cartId = context.request().param("id")
const cart = getCart(cartId)
const { checkoutURL } = getRedirectUrl(cartId)
const loginUrl = getLoginUrl(
loginClaims.bigCommerceId,
bigcommerceStoreHash,
bigcommerceBaseURL,
bigCommerceCliendId,
bigCommerceClientSecret,
checkoutURL.split(bigcommerceBaseURL).pop()
)
throw {
status: 200,
data: cart && {
cart: mapRestApiCartToGraphQL(cart),
checkoutURL: loginUrl,
},
}
})
}
}
const cartId = context.request().param("id")
const cart = getCart(cartId)
const { checkoutURL, embeddedCheckoutURL } = getRedirectUrl(cartId)
throw {
status: 200,
data: cart && {
cart: mapRestApiCartToGraphQL(cart),
checkoutURL: checkoutURL,
embeddedCheckoutURL: embeddedCheckoutURL,
},
}
})()

View File

@@ -1,53 +0,0 @@
/**
*
* @param {RestApiCart} cart
* @returns {BigCommerceCart}
*/
function mapRestApiCartToGraphQL(cart) {
return {
entityId: cart.id,
currencyCode: cart.currency.code,
isTaxIncluded: cart.tax_included,
baseAmount: { value: cart.base_amount, currencyCode: cart.currency.code },
discountedAmount: { value: cart.discount_amount, currencyCode: cart.currency.code },
amount: { value: cart.cart_amount, currencyCode: cart.currency.code },
discounts: cart.discounts.map((d) => ({
entityId: d.id,
discountedAmount: { value: d.discounted_amount, currencyCode: cart.currency.code },
})),
lineItems: {
physicalItems: cart.line_items.physical_items.map((item) => ({
entityId: item.id,
parentEntityId: cart.parent_id,
productEntityId: item.product_id,
variantEntityId: item.variant_id,
sku: item.sku,
name: item.name,
url: item.url,
imageUrl: item.image_url,
brand: "BKDF", // REST API does not provide brand directly
quantity: item.quantity,
isTaxable: item.is_taxable,
listPrice: { value: item.list_price, currencyCode: cart.currency.code },
extendedListPrice: { value: item.extended_list_price, currencyCode: cart.currency.code },
selectedOptions: item?.options?.map((option) => ({
// Map each option to the GraphQL format
// Assuming a simplified structure here for the example
})),
isShippingRequired: true, // Example assumption
})),
digitalItems: [],
customItems: [],
giftCertificates: [],
totalQuantity: cart.line_items.physical_items.reduce((total, item) => total + item.quantity, 0),
},
createdAt: { utc: new Date(cart.created_time) },
updatedAt: { utc: new Date(cart.updated_time) },
locale: cart.locale,
}
}
module.exports = {
mapRestApiCartToGraphQL,
}

View File

@@ -1,25 +0,0 @@
const { addCartItem, updateCartItem, deleteCartItem, getCart } = require("../lib/bigcommerceRestAPI")
const { postAddToCart } = require("../lib/facebookRestAPI")
;(function () {
const operation = context?.data?.operation
if (operation == "add") {
addCartItem(context?.data?.cartId, context?.data?.lineItems)
try {
postAddToCart()
} catch (e) {}
} else if (operation == "update") {
updateCartItem(context?.data?.cartId, context?.data?.lineItem, context?.data?.entityId)
try {
postAddToCart()
} catch (e) {}
} else if (operation == "delete") {
deleteCartItem(context?.data?.cartId, context?.data?.entityId)
}
throw {
status: 200,
message: "success",
}
})()

View File

@@ -1,55 +0,0 @@
const { getCustomerAddressById, getCustomerAddresses, getCustomerById } = require("../lib/bigcommerceRestAPI")
const { withAccount } = require("../lib/utils")
;(function () {
console.log("wtf")
if (context.user.auth()) return
const request = context.request()
const queryUsername = request.query("username")
if (queryUsername) {
const user = context.db.find("bigCommerceCustomer", {
filter: { username: queryUsername },
})[0]
if (!user) {
throw { status: 404, error: "customer not found" }
}
throw {
data: {
id: user.id,
username: user.username,
socialMediaAccounts: user.socialMediaAccounts,
personalRecords: user.personalRecords,
},
status: 200,
}
}
const foreign = request.query("foreign")
if (foreign) {
const id = request.param("id")
const user = context.db.find("bigCommerceCustomer", {
filter: { _id: id },
})[0]
if (!user) {
throw { status: 404, error: "customer not found" }
}
throw {
data: {
id: user.id,
username: user.username,
socialMediaAccounts: user.socialMediaAccounts,
personalRecords: user.personalRecords,
},
status: 200,
}
}
withAccount((loginClaims) => {
const queryAddressId = request.query("address")
const queryAddresses = request.query("addresses")
if (queryAddressId) {
throw { data: getCustomerAddressById(loginClaims.bigCommerceId, Number(queryAddressId)), status: 200 }
} else if (queryAddresses) {
throw { data: getCustomerAddresses(loginClaims.bigCommerceId), status: 200 }
} else throw { data: getCustomerById(loginClaims.bigCommerceId), status: 200 }
})
throw { status: 401, error: "unauthorized", log: false }
})()

View File

@@ -1,250 +0,0 @@
const {
updateCustomer,
updateCustomerAddressById,
deleteCustomerAddressById,
addCustomerAddress,
validateCredentials,
getCustomerById,
} = require("../lib/bigcommerceRestAPI")
const { withAccount } = require("../lib/utils")
const { getJwt } = require("../lib/utils")
const emailRegex =
/(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/
;(function () {
if (context.user.auth()) return
let data = context.data
const r = context.request()
const customerId = r.param("id")
const backendAuth = context.user.auth()
let returnCustomer = undefined
if (!backendAuth) {
// require authorization header with jwt
const token = getJwt(context)
/** @type {JWTPwResetClaims} */ // @ts-ignore
const pwResetClaims = token.claims
/** @type {Customer} */ // @ts-ignore
const customer = context.db.find("bigCommerceCustomer", {
filter: { _id: pwResetClaims.tibiId },
})[0]
if (!customer) {
throw {
status: 404,
error: "customer not found",
log: false,
}
}
if (data.operation === "resetPassword") {
if (pwResetClaims && pwResetClaims.tibiId && pwResetClaims.check) {
// is password reset token since it has .check
if (pwResetClaims.tibiId != customerId)
throw {
status: 403,
error: "token not valid for this id",
log: false,
}
if (pwResetClaims.check != customer.currentToken)
throw {
status: 403,
error: "password reset token expired",
log: false,
}
updateCustomer({
authentication: {
force_password_reset: false,
new_password: data.password,
},
id: Number(customer.bigCommerceId),
})
throw {
status: 200,
log: false,
}
} else {
throw {
status: 403,
error: "invalid token",
log: false,
}
}
}
withAccount((loginClaims) => {
/** @type {BigCommerceCustomer} */
const customer = data.customer
if (customer) customer.id = Number(loginClaims.bigCommerceId)
if (data.operation === "updateCustomer") {
customer.email = loginClaims.email
const index = customer.form_fields.findIndex((f) => f.name === "username")
const internalCustomer = context.db.find("bigCommerceCustomer", {
filter: { email: customer.email },
})[0]
if (index >= 0) customer.form_fields[index].value = internalCustomer.username
const response = updateCustomer(customer)
throw {
status: 200,
data: response,
message: "customer updated",
log: false,
}
} else if (data.operation == "updateInternalCustomer") {
const customer = data.customer
returnCustomer = customer
} else if (data.operation === "updateCustomerAddress") {
/** @type {BigCommerceAddress} */
const address = data.address
address.customer_id = Number(loginClaims.bigCommerceId)
const response = updateCustomerAddressById(address)
throw {
status: 200,
message: "customer addresses updated",
data: response,
log: false,
}
} else if (data.operation === "deleteCustomerAddress") {
deleteCustomerAddressById(Number(loginClaims.bigCommerceId), data.addressId)
throw {
status: 200,
message: "customer address deleted",
data: {
success: true,
},
log: false,
}
} else if (data.operation === "addCustomerAddress") {
/** @type {BigCommerceAddress} */
const address = data.address
address.customer_id = Number(loginClaims.bigCommerceId)
delete address.id
const response = addCustomerAddress(address)
throw {
status: 200,
message: "customer addresses updated",
data: response,
log: false,
}
} else if (data.operation === "changePassword") {
const res = validateCredentials(loginClaims.email, data.currentPassword)
if (!res.is_valid) {
throw {
status: 403,
error: "current password incorrect",
log: false,
}
}
const resPw = updateCustomer({
authentication: {
force_password_reset: false,
new_password: data.newPassword,
},
id: Number(loginClaims.bigCommerceId),
})
throw {
status: 200,
message: "password reset successful",
log: false,
}
} else if (data.operation === "updateEmail") {
const resValidation = validateCredentials(loginClaims.email, data.password)
if (!resValidation.is_valid) {
throw {
status: 403,
error: "password incorrect",
log: false,
}
}
if (!data.email)
throw {
status: 400,
error: "email is required",
log: false,
}
data.email = data.email.toLowerCase()
const customers = context.db.find("bigCommerceCustomer", {
filter: { email: data.email },
})
if (customers.length > 0)
throw {
status: 409,
error: "email already in use",
log: false,
}
if (emailRegex.test(data.email) === false) {
throw {
status: 400,
error: "invalid email",
log: false,
}
}
const res = updateCustomer({ id: Number(loginClaims.bigCommerceId), email: data.email })
context.db.update("bigCommerceCustomer", loginClaims.tibiId, { email: data.email })
throw {
status: 200,
message: "customer updated",
data: res,
log: false,
}
} else if (data.operation === "updateUsername") {
const resValidation = validateCredentials(loginClaims.email, data.password)
if (!resValidation.is_valid) {
throw {
status: 403,
error: "password incorrect",
log: false,
}
}
/**@type {string} */
let username = data.username
if (!username)
throw {
status: 400,
error: "username is required",
log: false,
}
username = username.toLowerCase()
const userWithUsername = context.db.find("bigCommerceCustomer", {
filter: { username: username },
})[0]
if (userWithUsername)
throw {
status: 409,
message: "username already in use",
}
const internalCustomer = context.db.find("bigCommerceCustomer", {
filter: { email: loginClaims.email },
})[0]
internalCustomer.username = username
if (internalCustomer) {
context.db.update("bigCommerceCustomer", internalCustomer.id, internalCustomer)
}
//update bigcommerce customer too : get it and then update it with username swapped
const bCCustomer = getCustomerById(loginClaims.bigCommerceId)
if (!bCCustomer.form_fields) bCCustomer.form_fields = []
const usernameIndex = bCCustomer.form_fields.findIndex((f) => f.name === "username")
if (usernameIndex >= 0) bCCustomer.form_fields[usernameIndex].value = username
else bCCustomer.form_fields = [...bCCustomer.form_fields, { name: "username", value: username }]
updateCustomer(bCCustomer)
throw {
status: 200,
message: "customer updated",
data: internalCustomer,
log: false,
}
}
})
}
if (returnCustomer) {
return {
data: returnCustomer,
}
}
throw { status: 401, error: "unauthorized", log: false }
})()

View File

@@ -1,56 +0,0 @@
const { getOrderById, getOrdersForCustomer, createInternalOrderObject } = require("../lib/bigcommerceRestAPI")
const { withAccount } = require("../lib/utils")
;(function () {
if (context.user.auth()) return {}
withAccount((login) => {
const orderId = context.request().param("id")
if (orderId) {
/** @type {Order} */
// @ts-ignore
const internalOrder = context.db.find("bigCommerceOrder", {
filter: { _id: orderId },
})[0]
if (internalOrder.customerTibiId !== login.tibiId) {
if (String(internalOrder?.customerBigCommerceId) !== String(login?.bigCommerceId)) {
throw {
message: "You don't have permission to access this order",
code: 403,
}
} else {
context.db.update("bigCommerceOrder", internalOrder.id, {
customerTibiId: login.tibiId,
})
}
}
const order = getOrderById(internalOrder.bigCommerceId)
order.tibiId = internalOrder.id
order.status = internalOrder.status
order.shipments = internalOrder.shipments
order.statusSetAt = internalOrder.statusSetAt
throw {
data: order,
status: 200,
}
} else {
const orders = getOrdersForCustomer(login.bigCommerceId)
orders.forEach((order) => {
let internalOrder = context.db.find("bigCommerceOrder", {
filter: { bigCommerceId: order.id },
})[0]
if (!internalOrder && order.id) {
const internalOrderReference = createInternalOrderObject(order.id)
internalOrder = context.db.create("bigCommerceOrder", internalOrderReference)
} else if (!internalOrder) return
order.tibiId = internalOrder?.id
order.status = internalOrder?.status
order.shipments = internalOrder?.shipments
order.statusSetAt = internalOrder?.statusSetAt
})
throw {
data: orders,
status: 200,
}
}
})
})()

View File

@@ -1,5 +0,0 @@
import { withAccount } from "../lib/utils"
;(function () {
if (context.user.auth()) return {}
withAccount((login) => {})
})

View File

@@ -1,47 +0,0 @@
const { withAccount } = require("../lib/utils")
;(function () {
withAccount((login) => {
const id = context.request().param("id")
if (!id) {
throw {
message: "id is required",
code: 400,
}
}
const existingReturnRequest = context.db.find("orderReturnRequest", {
filter: {
_id: id,
},
})[0]
if (!existingReturnRequest)
throw {
message: "Return request not found",
code: 404,
}
const order = context.db.find("bigCommerceOrder", {
filter: {
bigCommerceId: Number(existingReturnRequest.bigCommerceId),
},
})[0]
if (!order)
throw {
message: "Order not found",
code: 404,
}
if (order.customerBigCommerceId !== login.bigCommerceId)
throw {
message: "You don't have permission to access this order",
code: 403,
}
if (
existingReturnRequest.status !== "pending" &&
existingReturnRequest.status !== "approved" &&
!!existingReturnRequest.status
)
throw {
message: "Return request is not pending or approved",
code: 400,
}
})
})()

View File

@@ -1,33 +0,0 @@
const { serverBaseURL, logoPath, operatorEmail, noReplyEmail, contactEmail } = require("../config")
const { frontendBase } = require("../config-client")
const { withAccount } = require("../lib/utils")
;(function () {
if (context.user.auth()) return
withAccount((login) => {
const store = {
logo: `${serverBaseURL}${logoPath}`,
frontendBase,
}
const html = context.tpl.execute(context.fs.readFile("templates/orderReturnRequestRevoked.html"), {
store,
})
context.smtp.sendMail({
to: contactEmail,
from: noReplyEmail,
fromName: "BinKrassDuFass",
replyTo: noReplyEmail,
subject: "New Order Return Request Cancellation",
plain: `Die Bestellung von ${login.email} soll doch nicht zurückgegeben werden.`,
})
context.smtp.sendMail({
to: login.email,
from: operatorEmail,
fromName: "BinKrassDuFass",
replyTo: noReplyEmail,
subject: "Widerruf deiner Bestellung abgebrochen",
html,
})
})
})()

View File

@@ -1,41 +0,0 @@
const { withAccount } = require("../lib/utils")
;(function () {
const queryParam = context.request().query("orderId")
if (!queryParam) {
if (context.user.auth()) return
throw {
message: "orderId is required",
code: 400,
}
}
withAccount((login) => {
const order = context.db.find("bigCommerceOrder", {
filter: {
bigCommerceId: Number(queryParam),
},
})[0]
if (!order) {
throw {
message: "Order not found",
code: 404,
}
}
if (order.customerBigCommerceId !== login.bigCommerceId) {
console.log(
"order.customerBigCommerceId",
order.customerBigCommerceId,
"login.bigCommerceId",
login.bigCommerceId
)
throw {
message: "You don't have permission to access this order",
code: 403,
}
}
context.filter = {
bigCommerceId: order.bigCommerceId,
}
})
return context
})()

View File

@@ -1,81 +0,0 @@
const { withAccount } = require("../lib/utils")
const { getOrderProductsById } = require("../lib/bigcommerceRestAPI")
;(function () {
if (context.user.auth()) return
withAccount((login) => {
const order = context.db.find("bigCommerceOrder", {
filter: {
bigCommerceId: Number(context.data.bigCommerceId),
},
})[0]
if (!order)
throw {
message: "Order not found",
code: 404,
}
if (order.customerBigCommerceId !== login.bigCommerceId)
throw {
message: "You don't have permission to access this order",
code: 403,
}
const bigcommerceProducts = getOrderProductsById(context.data.bigCommerceId)
const returnRequestProducts = context.data.products
// make sure all ids are also in the orderproducts
returnRequestProducts.forEach((/** @type {OrderReturnRequestProduct} */ returnRequestProduct) => {
const bigcommerceProduct = bigcommerceProducts.find(
(bigcommerceProduct) => Number(bigcommerceProduct.id) === returnRequestProduct.productId
)
if (!bigcommerceProduct)
throw {
message: "Product not found",
code: 404,
}
if (bigcommerceProduct.quantity < returnRequestProduct.quantity)
throw {
message: "Quantity exceeds the available quantity",
code: 400,
}
const existingReturnRequests = context.db.find("orderReturnRequest", {
filter: {
bigCommerceId: context.data.bigCommerceId,
$or: [
{
status: "pending",
},
{
status: "approved",
},
{
status: "refunded",
},
],
},
})
returnRequestProduct.baseProductId = bigcommerceProduct.product_id
let totalExistingQuantity = 0
existingReturnRequests.forEach((existingReturnRequest) => {
existingReturnRequest.products.forEach(
(/** @type {OrderReturnRequestProduct} */ existingReturnRequestProduct) => {
if (existingReturnRequestProduct.productId === returnRequestProduct.productId) {
totalExistingQuantity += existingReturnRequestProduct.quantity
}
}
)
})
if (totalExistingQuantity + returnRequestProduct.quantity > bigcommerceProduct.quantity) {
throw {
message: "Quantity exceeds the available quantity",
code: 400,
}
}
})
context.data.products = context.data.products.filter(
(/** @type {OrderReturnRequestProduct} */ p) => p.quantity > 0
)
context.data.status = "pending"
context.data.email = login.email
})
return context
})()

View File

@@ -1,30 +0,0 @@
const { serverBaseURL, logoPath, operatorEmail, noReplyEmail, contactEmail } = require("../config")
const { frontendBase } = require("../config-client")
;(function () {
const store = {
logo: `${serverBaseURL}${logoPath}`,
frontendBase,
}
const html = context.tpl.execute(context.fs.readFile("templates/orderReturnRequestApproval.html"), {
store,
})
context.smtp.sendMail({
to: contactEmail,
from: noReplyEmail,
fromName: "BinKrassDuFass",
replyTo: noReplyEmail,
subject: "New Order Return Request",
plain: `Die Bestellung von ${context.data.email} soll zurückgegeben werden.`,
})
context.smtp.sendMail({
to: context.data.email,
from: operatorEmail,
fromName: "BinKrassDuFass",
replyTo: noReplyEmail,
subject: "Widerruf deiner Bestellung",
html,
})
})()

View File

@@ -1,51 +0,0 @@
const { logoPath, noReplyEmail, operatorEmail, serverBaseURL } = require("../config")
const { frontendBase } = require("../config-client")
const { getOrderById, updateOrderById } = require("../lib/bigcommerceRestAPI")
;(function () {
const oldOrderReturnRequest = context.db.find("orderReturnRequest", {
filter: {
_id: context.data.id,
},
})[0]
if (!oldOrderReturnRequest)
throw {
message: "Order return request not found",
code: 404,
}
if (context.data.status == "approved") {
const bigCommerceOrder = getOrderById(context.data.bigCommerceId)
delete bigCommerceOrder.productObjs
delete bigCommerceOrder.shipping_addressObjs
bigCommerceOrder.status_id = 5
updateOrderById(bigCommerceOrder.id, { status_id: 5 })
}
if (oldOrderReturnRequest.status !== context.data.status) {
const oRR = context.data
const customer = context.db.find("bigCommerceCustomer", {
filter: { email: oRR.email },
})[0]
if (!customer) {
throw {
status: 404,
message: "Customer not found",
}
}
const store = {
logo: `${serverBaseURL}${logoPath}`,
frontendBase,
}
const html = context.tpl.execute(context.fs.readFile("templates/statusUpdateReturnRequest.html"), {
store,
})
context.smtp.sendMail({
to: oRR.email,
from: operatorEmail,
fromName: "BinKrassDuFass",
replyTo: noReplyEmail,
subject: "Stornierung deiner Bestellung",
html,
})
}
})()

View File

@@ -1,35 +0,0 @@
const { withAccount } = require("../lib/utils")
;(function () {
const queryParam = context.request().query("orderId")
if (!queryParam) {
if (context.user.auth()) return
throw {
message: "orderId is required",
code: 400,
}
}
withAccount((login) => {
const order = context.db.find("bigCommerceOrder", {
filter: {
bigCommerceId: Number(queryParam),
},
})[0]
if (!order) {
throw {
message: "Order not found",
code: 404,
}
}
if (order.customerBigCommerceId !== login.bigCommerceId) {
throw {
message: "You don't have permission to access this order",
code: 403,
}
}
context.filter = {
bigCommerceId: order.bigCommerceId,
}
})
return context
})()

View File

@@ -1,45 +0,0 @@
const { withAccount } = require("../lib/utils")
const { getPrintfulOrder, cancelPrintfulOrder } = require("../lib/printfulRestAPI")
;(function () {
withAccount((login) => {
const order = context.db.find("bigCommerceOrder", {
filter: {
bigCommerceId: Number(context.data.bigCommerceId),
},
})[0]
if (!order)
throw {
message: "Order not found",
code: 404,
}
if (order.customerBigCommerceId !== login.bigCommerceId)
throw {
message: "You don't have permission to access this order",
code: 403,
}
const existingRevokeRequests = context.db.find("orderRevokeRequest", {
filter: {
bigCommerceId: context.data.bigCommerceId,
},
})
if (existingRevokeRequests.length > 0)
throw {
message: "Revoke request already exists",
code: 400,
}
if (!!order.status && order.status !== "draft") {
throw {
message: "Order is already in process",
code: 400,
}
}
const printfulOrder = getPrintfulOrder(order.bigCommerceId)
cancelPrintfulOrder(context.data.bigCommerceId)
context.data.status = "pending"
context.data.email = login.email
context.data.printfulId = printfulOrder.id
})
return context
})()

View File

@@ -1,36 +0,0 @@
const { serverBaseURL, logoPath, operatorEmail, noReplyEmail, contactEmail } = require("../config")
const { frontendBase } = require("../config-client")
const { getOrderById, updateOrderById } = require("../lib/bigcommerceRestAPI")
;(function () {
if (context.user.auth()) return
const bigCommerceOrder = getOrderById(context.data.bigCommerceId)
delete bigCommerceOrder.productObjs
delete bigCommerceOrder.shipping_addressObjs
bigCommerceOrder.status_id = 5
updateOrderById(bigCommerceOrder.id, { status_id: 5 })
const store = {
logo: `${serverBaseURL}${logoPath}`,
frontendBase,
}
const html = context.tpl.execute(context.fs.readFile("templates/orderRevokedApproval.html"), {
store,
})
context.smtp.sendMail({
to: contactEmail,
from: noReplyEmail,
fromName: "BinKrassDuFass",
replyTo: noReplyEmail,
subject: "New Order Revoke Request",
plain: `Die Bestellung von ${context.data.email} wurde abgebrochen.`,
})
context.smtp.sendMail({
to: context.data.email,
from: operatorEmail,
fromName: "BinKrassDuFass",
replyTo: noReplyEmail,
subject: "Abbruch deiner Bestellung",
html,
})
})()

View File

@@ -1,43 +0,0 @@
import { logoPath, noReplyEmail, operatorEmail, serverBaseURL } from "../config"
import { frontendBase } from "../config-client"
;(function () {
const oldOrderReturnRequest = context.db.find("orderReturnRequest", {
filter: {
_id: context.data.id,
},
})[0]
if (!oldOrderReturnRequest)
throw {
message: "Order return request not found",
code: 404,
}
if (oldOrderReturnRequest.status !== context.data.status) {
const oRR = context.data
const customer = context.db.find("bigCommerceCustomer", {
filter: { email: oRR.email },
})[0]
if (!customer) {
throw {
status: 404,
message: "Customer not found",
}
}
const store = {
logo: `${serverBaseURL}${logoPath}`,
frontendBase,
}
const html = context.tpl.execute(context.fs.readFile("templates/statusUpdateReturnRequest.html"), {
store,
})
context.smtp.sendMail({
to: oRR.email,
from: operatorEmail,
fromName: "BinKrassDuFass",
replyTo: noReplyEmail,
subject: "Stornierung Ihrer Bestellung",
html,
})
}
})()

View File

@@ -1,23 +0,0 @@
const { attachRatingObjsToProduct } = require("./helper")
;(function () {
if (context.user.auth()) return
const bigCommerceProductId = context.request().query("bigCommerceProductId")
if (bigCommerceProductId) {
/**@type {LocalProduct[]} */
//@ts-ignore
let products = context.db.find("bigCommerceProduct", {
filter: {
bigCommerceId: Number(bigCommerceProductId),
},
})
products = attachRatingObjsToProduct(products)
if (products.length > 0) {
throw {
status: 200,
data: products[0],
}
}
}
return
})()

View File

@@ -1,12 +0,0 @@
const { attachRatingObjsToProduct } = require("./helper")
;(function () {
if (context.user.auth()) return
/**@type {LocalProduct[]} */
//@ts-ignore
let products = context.results()
products = attachRatingObjsToProduct(products)
let hookResponse = {
results: products,
}
return hookResponse
})()

View File

@@ -1,26 +0,0 @@
/**
* @param {LocalProduct[]} products
* @returns {LocalProduct[]}
*/
function attachRatingObjsToProduct(products) {
let productIds = products.map((product) => product.bigCommerceId)
/**@type {ProductRating[]} */
//@ts-ignore
let allRatings = context.db.find("rating", {
filter: {
status: "approved",
bigCommerceProductId: { $in: productIds },
},
})
products.forEach((product, i) => {
let ratings = allRatings.filter((rating) => rating.bigCommerceProductId === product.bigCommerceId).reverse()
products[i].ratings = ratings
})
return products
}
module.exports = {
attachRatingObjsToProduct,
}

View File

@@ -1,36 +0,0 @@
const { getAllProducts, getProductImages } = require("../lib/bigcommerceRestAPI")
const { extractSizingChart } = require("../lib/printfulRestAPI")
;(function () {
const products = getAllProducts()
const productIds = products.map((p) => p.id)
const currentProducts = context.db.find("bigCommerceProduct", { filter: { bigCommerceId: { $in: productIds } } })
const notFoundProductIds = productIds.filter((id) => !currentProducts.some((p) => p.bigCommerceId == id))
const newProducts = products.filter((p) => notFoundProductIds.includes(p.id))
newProducts.forEach((p) => {
const productImage = getProductImages(String(p.id))
context.db.create("bigCommerceProduct", {
forcedWarning: "",
productName: p.name_customer || p.name,
previewImage: productImage?.[0]?.url_thumbnail,
bigCommerceId: p?.id,
sizingChart: null,
})
})
currentProducts.forEach((p) => {
context.db.update("bigCommerceProduct", p.id, {
...p,
productName:
products.find((bp) => bp.id == p.bigCommerceId).name_customer ||
products.find((bp) => bp.id == p.bigCommerceId).name,
bigCommerceSKU: products.find((bp) => bp.id == p.bigCommerceId).sku,
bigCommerceId: products.find((bp) => bp.id == p.bigCommerceId).id,
sizingChart: extractSizingChart(p.printfulProductId, p.id),
})
})
throw {
status: 200,
message: "Products created or updated successfully",
}
})()

View File

@@ -1,13 +0,0 @@
const { extractSizingChart } = require("../lib/printfulRestAPI")
let { clearSSRCache } = require("../lib/utils")
;(function () {
clearSSRCache()
if (context.data.printfulProductId) {
const sizingChart = extractSizingChart(context.data.printfulProductId, context.data.id)
if (sizingChart) {
context.data.sizingChart = sizingChart
return { data: context.data }
}
}
})()

View File

@@ -1,15 +0,0 @@
;(function () {
const ratingId = context.request().param("id")
let rating = context.db.find("rating", {
filter: {
_id: ratingId,
},
})[0]
if (!rating.id)
throw {
status: 400,
error: "No id specified.",
}
// @ts-ignore
context["rating"] = rating
})()

View File

@@ -1,7 +0,0 @@
let { clearSSRCache } = require("../lib/utils")
let { deleteRating } = require("../lib/bigcommerceRestAPI")
;(function () {
clearSSRCache()
// @ts-ignore
deleteRating(context["rating"].bigCommerceProductId, context["rating"].bigcommerceReviewId)
})()

View File

@@ -1,46 +0,0 @@
// @ts-check
const { withAccount } = require("../lib/utils")
;(function () {
/** @type {HookResponse} */
let hookResponse
let request = context.request()
if (context.user.auth()) return
const orderId = Number(request.query("orderId"))
if (orderId) {
withAccount((login) => {
let order = context.db.find("bigCommerceOrder", {
filter: {
bigCommerceId: orderId,
customerBigCommerceId: login.bigCommerceId,
},
})[0]
if (!order) {
throw {
status: 404,
data: {
message: "Order not found",
},
}
}
})
hookResponse = {
filter: {
bigcommerceOrderId: orderId,
},
}
return hookResponse
} else {
hookResponse = {
filter: context.filter,
selector: {
bigCommerceProductId: 1,
rating: 1,
comment: 1,
title: 1,
review_date: 1,
},
}
return hookResponse
}
})()

View File

@@ -1,17 +0,0 @@
const { productInsideOrder } = require("../lib/utils")
;(function () {
if (!context?.user?.auth()?.id) {
if (!context.data) throw { status: 400, error: "No data provided" }
// @ts-ignore
productInsideOrder(context.data)
/** @type {ProductRating[]} */ // @ts-ignore
let ratings = context.db.find("rating", {
filter: {
bigcommerceOrderId: context?.data?.bigcommerceOrderId,
bigCommerceProductId: context?.data?.bigCommerceProductId,
},
})
if (ratings.length) throw { status: 400, error: "Rating already existing" }
}
})()

View File

@@ -1,41 +0,0 @@
const { bigcommerceApiOAuth, serverBaseURL, bigcommerceStoreHash } = require("../config.js")
let { sendOperatorRatingMail, clearSSRCache, statusIsValid } = require("../lib/utils")
;(function () {
const response = context.http.fetch(
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products/${context?.data?.bigCommerceProductId}/reviews`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify({
title: context?.data?.title,
comment: context?.data?.comment,
status:
context?.data?.status === "approved"
? "approved"
: context?.data?.status == "pending"
? "pending"
: "disapproved",
rating: context?.data?.rating?.overall,
date_reviewed: context?.data?.review_date,
}),
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
const bigcommerceRating = response.body.json()
context.db.update("rating", context?.data?.id || "", {
bigcommerceReviewId: String(bigcommerceRating?.data?.id),
})
clearSSRCache()
sendOperatorRatingMail()
})()

View File

@@ -1,5 +0,0 @@
let { validateAndModifyRating } = require("../lib/utils")
;(function () {
// @ts-ignore
return { data: validateAndModifyRating(context.data) }
})()

View File

@@ -1,42 +0,0 @@
let { sendOperatorRatingMail, clearSSRCache, statusIsValid } = require("../lib/utils")
const { bigcommerceApiOAuth, bigcommerceStoreHash } = require("../config.js")
;(function () {
const response = context.http.fetch(
// @ts-ignore
`https://api.bigcommerce.com/stores/${bigcommerceStoreHash}/v3/catalog/products/${context?.data?.bigCommerceProductId}/reviews/${context["oldRating"]?.bigcommerceReviewId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
"X-Auth-Token": bigcommerceApiOAuth,
},
body: JSON.stringify({
title: context?.data?.title,
comment: context?.data?.comment,
status:
context?.data?.status === "approved"
? "approved"
: context?.data?.status == "pending"
? "pending"
: "disapproved",
rating: context?.data?.rating?.overall,
date_reviewed: context?.data?.review_date,
}),
}
)
if (!statusIsValid(response.status)) {
throw {
status: response.status,
error: response.statusText,
}
}
let rating = context.data
// @ts-ignore
let oldRating = context["oldRating"]
// @ts-ignore
if (!oldRating || JSON.stringify(rating) != JSON.stringify(oldRating)) {
sendOperatorRatingMail()
clearSSRCache()
}
})()

View File

@@ -1,16 +0,0 @@
const { productInsideOrder } = require("../lib/utils")
;(function () {
if (!context?.user?.auth()?.id) {
// @ts-ignore
productInsideOrder(context?.data)
}
/** @type {ProductRating} */ // @ts-ignore
let ratingObj = context.db.find("rating", {
filter: {
_id: context?.data?.id,
},
})[0]
// @ts-ignore
context["oldRating"] = ratingObj
})()

View File

@@ -1,5 +0,0 @@
let { validateAndModifyRating } = require("../lib/utils")
;(function () {
// @ts-ignore
return { data: validateAndModifyRating(context.data) }
})()

View File

@@ -1 +0,0 @@
ich arbeite jetzt mit so nhm tool zusammen, was mir das figma als svelte + css ausspuckt, ziemlich redundant. Deine aufgabe ist in diesem chat, die sachen zu nehmen, das css ist less umzuwandeln und redundanzen aus dem less zu entfernen (keine neuen variablen machen!!) und im template mit loops etc arebbeiten sowie im script block halt arrays etc declaren... Schreib unbedingt immer alles aus, niemals iwi ... oder so machen, wenn nötig, auch über mehrere antworten lnag ziehen, ich wills copyn pasten können. Die seite bleibt nicht statisch, ich baue das dann selbst so um, dass die daten dynamisch gefatched werden, baue es so, dass ich da ein leichtes habe. Achte beim HTML vor allem auf ausgezeichnete semantik! Benutze weiterhin die variablen, die auch im bereitgestelltem skript gegeben sind, die sind alle verfügbar:

View File

@@ -1,71 +0,0 @@
version: "3.9"
services:
maildev:
image: maildev/maildev
command: [node, bin/maildev, --web, "80", --smtp, "25", -v, --hide-extensions=STARTTLS]
expose:
- 1080
mongo:
image: mongo:4.2
tibi-server:
image: gitbase.de/cms/tibi-server
environment:
DB_DIAL: mongodb://mongo
API_PORT: 8080
MAIL_HOST: maildev:25
depends_on:
- maildev
- mongo
volumes:
- ./:/workdir
liveserver:
image: node:20
command: yarn run -- live-server --no-browser --port=80 --ignore='*' --entry-file=spa.html --no-css-inject --proxy=/api:http://tibi-server:8080/api/v1/_/einfo_test frontend
depends_on:
- tibi-server
volumes:
- ./:/workdir
working_dir: /workdir
playwright:
image: mcr.microsoft.com/playwright:focal
environment:
FORCE_COLOR: "true"
PLAYWRIGHT_BASE_URL: http://liveserver
PLAYWRIGHT_CI: "true"
PLAYWRIGHT_mongodbUri: mongodb://mongo
PLAYWRIGHT_tibiApiUrl: http://tibi-server:8080/api/v1
PLAYWRIGHT_projectApiConfig: /workdir/api/config.yml
DISPLAY: display:0
PLAYWRIGHT_CACHE_FOLDER: /.cache/Playwright
command: sh ./scripts/py-command.docker.sh $PY_MODE $PY_SPECFLAG
depends_on:
- liveserver
volumes:
- ./:/workdir
- ./tmp/.npm:/.npm
- ./tmp/.cache:/.cache
- ./tmp/.yarn:/.yarn
working_dir: /workdir
display:
image: ghcr.io/dtinth/xtigervnc-docker:main
tmpfs: /tmp
restart: always
environment:
VNC_GEOMETRY: 1920x1080
novnc:
image: geek1011/easy-novnc
restart: always
command: -a :5800 -h display --no-url-password
labels:
- traefik.enable=true
- traefik.http.services.${PROJECT_NAME}-playwright-novnc.loadbalancer.server.port=5800
- online.testversion.code.subdomain=${PROJECT_NAME}-playwright-novnc
- traefik.http.routers.${PROJECT_NAME}-playwright-novnc.middlewares=${PROJECT_NAME}-playwright-novnc
- traefik.http.middlewares.${PROJECT_NAME}-playwright-novnc.basicauth.usersfile=${PWD}/.basic-auth-code

View File

@@ -25,11 +25,7 @@
import { getWishlist } from "./lib/functions/CommerceAPIs/tibiEndpoints/wishlist" import { getWishlist } from "./lib/functions/CommerceAPIs/tibiEndpoints/wishlist"
import PublicProfile from "./routes/PublicProfile.svelte" import PublicProfile from "./routes/PublicProfile.svelte"
import { api, getDBEntries } from "./api" import { api, getDBEntries } from "./api"
import NewsletterRow from "./lib/components/pagebuilder/blocks/NewsletterRow.svelte"
import HelpCenter from "./routes/HelpCenter.svelte" import HelpCenter from "./routes/HelpCenter.svelte"
import { checkIfStoreIsOpen } from "./lib/functions/CommerceAPIs/tibiEndpoints/store"
import Input from "./lib/components/pagebuilder/blocks/form/Input.svelte"
import { onChange } from "./lib/components/pagebuilder/profile/helper"
import RedirectToPublicProfile from "./routes/RedirectToPublicProfile.svelte" import RedirectToPublicProfile from "./routes/RedirectToPublicProfile.svelte"
import { onMount } from "svelte" import { onMount } from "svelte"
import ActionApproval from "./lib/components/widgets/ActionApproval.svelte" import ActionApproval from "./lib/components/widgets/ActionApproval.svelte"
@@ -54,7 +50,6 @@
if (l.push && oldPath != l.path) window.scrollTo(0, 0) if (l.push && oldPath != l.path) window.scrollTo(0, 0)
oldPath = l.path oldPath = l.path
}) })
getWishlist().then(wishlist.set)
api(`module`, { api(`module`, {
method: "GET", method: "GET",
@@ -62,30 +57,6 @@
$modules = res.data $modules = res.data
}) })
api("productBackgroundImage", {
method: "GET",
}).then((res) => {
res.data.forEach((element) => {
$backgroundImages[element.type] = element.image
})
})
checkIfStoreIsOpen().then((status) => {
if ($shopStatus.loggedIn) return
if (!status || status.status === "open") {
shopStatus.set({
status: "open",
loggedIn: true,
password: status?.password,
})
} else {
shopStatus.set({
status: "login",
loggedIn: false,
password: status.password,
})
}
})
onMount(() => { onMount(() => {
const updateModalState = () => { const updateModalState = () => {
$openModal = document.getElementsByClassName("dialog-open").length > 0 $openModal = document.getElementsByClassName("dialog-open").length > 0
@@ -93,21 +64,13 @@
const interval = setInterval(updateModalState, 100) const interval = setInterval(updateModalState, 100)
return () => clearInterval(interval) return () => clearInterval(interval)
}) })
let login = {
password: "",
}
let googleCookiesAllowed = false let googleCookiesAllowed = false
let googleCookieName = "googleAnalytics" let googleCookieName = "googleAnalytics"
let metaPixelCookieName = "metaPixel"
let metaPixelCookiesAllowed = false
getDBEntries("selfImprovementChapter").then((res) => {
$selfImprovementChapters = res
})
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
window.addEventListener("ccAccept", (e) => { window.addEventListener("ccAccept", (e) => {
// @ts-ignore // @ts-ignore
if (e.detail.includes(googleCookieName)) googleCookiesAllowed = true if (e.detail.includes(googleCookieName)) googleCookiesAllowed = true
if (e.detail.includes(metaPixelCookieName)) metaPixelCookiesAllowed = true
}) })
function checkCookie(cookieName: string) { function checkCookie(cookieName: string) {
var allCookies = decodeURIComponent(document.cookie) var allCookies = decodeURIComponent(document.cookie)
@@ -124,7 +87,6 @@
return false return false
} }
googleCookiesAllowed = checkCookie(googleCookieName) googleCookiesAllowed = checkCookie(googleCookieName)
metaPixelCookiesAllowed = checkCookie(metaPixelCookieName)
} }
let innerWidth = 0 let innerWidth = 0
@@ -135,14 +97,7 @@
<svelte:head> <svelte:head>
{#if googleCookiesAllowed} {#if googleCookiesAllowed}
<!-- Google tag (gtag.js) --> <!-- Google tag (gtag.js) -->
<!-- Google tag (gtag.js) -->
<!-- Google tag (gtag.js) -->
<!-- Google tag (gtag.js) -->
<!-- Google tag (gtag.js) -->
<!-- Google tag (gtag.js) -->
<!-- Google tag (gtag.js) -->
<!-- Google tag (gtag.js) -->
<!-- Google tag (gtag.js) -->
<script <script
async async
src="https://www.googletagmanager.com/gtag/js?id=G-SH85R88QE0" src="https://www.googletagmanager.com/gtag/js?id=G-SH85R88QE0"
@@ -157,55 +112,12 @@
gtag("config", "G-SH85R88QE0") gtag("config", "G-SH85R88QE0")
</script> </script>
{/if} {/if}
{#if metaPixelCookiesAllowed}
<!-- Meta Pixel Code -->
<!-- Meta Pixel Code -->
<!-- Meta Pixel Code -->
<!-- Meta Pixel Code -->
<!-- Meta Pixel Code -->
<!-- Meta Pixel Code -->
<!-- Meta Pixel Code -->
<!-- Meta Pixel Code -->
<!-- Meta Pixel Code -->
<script>
console.log("MetaPixelCookiesAllowed ist aktiv.")
!(function (f, b, e, v, n, t, s) {
if (f.fbq) return
n = f.fbq = function () {
n.callMethod ? n.callMethod.apply(n, arguments) : n.queue.push(arguments)
}
if (!f._fbq) f._fbq = n
n.push = n
n.loaded = !0
n.version = "2.0"
n.queue = []
t = b.createElement(e)
t.async = !0
t.src = v
s = b.getElementsByTagName(e)[0]
s.parentNode.insertBefore(t, s)
})(window, document, "script", "https://connect.facebook.net/en_US/fbevents.js")
fbq("init", "1117933239951751")
fbq("track", "PageView")
</script>
<noscript
><img
height="1"
width="1"
style="display: none"
src="https://www.facebook.com/tr?id=1117933239951751&ev=PageView&noscript=1"
/></noscript
>
<!-- End Meta Pixel Code -->
{/if}
</svelte:head> </svelte:head>
<div> <div>
<SidebarOverlay /> <SidebarOverlay />
<main> <main>
<Header /> <Header />
{#if $shopStatus.status === "open" || $shopStatus.loggedIn}
{#if $location?.path?.toLowerCase().startsWith("/product/")} {#if $location?.path?.toLowerCase().startsWith("/product/")}
{#key $location?.path} {#key $location?.path}
<Product handle="{$location.path.split('/product/')[1]}" /> <Product handle="{$location.path.split('/product/')[1]}" />
@@ -235,40 +147,7 @@
{:else} {:else}
<Content location="{$location}" /> <Content location="{$location}" />
{/if} {/if}
{:else}
<form
on:submit|preventDefault|stopPropagation="{() => {
if (login.password === $shopStatus.password) {
shopStatus.set({
status: 'login',
loggedIn: true,
password: login.password,
})
newNotification({
class: 'success',
html: 'Erfolgreich eingeloggt',
})
} else {
newNotification({
class: 'error',
html: 'Falsches Passwort',
})
}
}}"
>
<Input
id="password"
type="password"
bind:value="{login.password}"
placeholder="Passwort"
onChange="{onChange}"
/>
<button
class="cta primary"
type="submit">Login</button
>
</form>
{/if}
<div class="crossGap"></div> <div class="crossGap"></div>
<Footer /> <Footer />
</main> </main>

View File

@@ -307,20 +307,6 @@ type EntryTypeSwitch<T> = T extends "medialib"
? ContentEntry ? ContentEntry
: T extends "navigation" : T extends "navigation"
? NavigationEntry ? NavigationEntry
: T extends "tag"
? TagEntry
: T extends "dummyCartEndpoint"
? DummyCartEndpoint
: T extends "productBenefit"
? ProductBenefit
: T extends "selfImprovementChapter"
? SelfImprovementChapter
: T extends "rating"
? ProductRating
: T extends "bigCommerceProduct"
? LocalProduct
: T extends "selfImprovementChallenge"
? BKDFChallenge
: never : never
export async function getDBEntries<T extends CollectionName>( export async function getDBEntries<T extends CollectionName>(

View File

@@ -1,122 +0,0 @@
<script lang="ts">
import { getDBEntry } from "../../../api"
import CrinkledSection from "../CrinkledSection.svelte"
import ContentBlock from "../pagebuilder/ContentBlock.svelte"
import MedialibImage from "../widgets/MedialibImage.svelte"
export let entryId: string
export let thumbnail: string
export let sources: Source[]
let entry: ContentEntry
getDBEntry("content", { _id: entryId }).then((res) => {
entry = res
console.log("entry", entry)
})
</script>
<section class="blog-entry">
<section class="thumbnail-image">
<img
src="../../../../media/topRightCrinkleDarkAndUnedged.svg"
class="crinkle"
alt="crinkle"
/>
{#if entry}
<MedialibImage id="{thumbnail}" />
{/if}
</section>
<section class="content-wrapper">
<CrinkledSection
activated="{true}"
border="{false}"
brightBackground="{false}"
bigVersion="{true}"
icon="bottomLeftDark"
>
<section class="content">
{#if entry}
{#each entry.blocks as block}
<ContentBlock block="{block}" />
{/each}
{/if}
</section>
<section class="sources">
<h4>Quellen:</h4>
<ul>
{#each sources as { source, url }}
<li>
<a
href="{url}"
target="_blank"
>
{source}
</a>
</li>
{/each}
</ul>
</section>
</CrinkledSection>
</section>
</section>
<style
lang="less"
global
>
.blog-entry {
display: flex;
flex-direction: column;
max-width: var(--small-max-width);
width: 100%;
.thumbnail-image {
width: 100%;
height: 520px;
position: relative;
margin-botom: -3.6rem;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.crinkle {
position: absolute;
top: 0;
right: 0;
width: 72px;
height: 72px;
z-index: 1;
}
}
.content-wrapper {
padding: 0px 2.4rem;
.content {
display: flex;
flex-direction: column;
gap: 2.4rem;
background: var(--bg-100);
width: 100%;
* {
color: var(--text-100) !important;
}
}
.container {
margin-bottom: 0px !important;
}
.sources {
display: flex;
flex-direction: column;
width: 100%;
color: var(--text-100);
padding: 1.2rem 2.4rem;
border-top: 1px solid var(--neutral-white);
margin-top: 1.2rem;
h4,
a {
padding: 0.6rem 0px;
color: var(--text-100);
}
}
}
}
</style>

View File

@@ -1,133 +0,0 @@
<script lang="ts">
import { getCachedEntry } from "../../../api"
import { getCalendarWeekFromDate } from "../../utils"
import Steps from "../pagebuilder/blocks/Steps.svelte"
import BlogTabSwitch from "../widgets/BlogTabSwitch.svelte"
import MedialibImage from "../widgets/MedialibImage.svelte"
import ChallengeBlog from "./Blog.svelte"
export let slug: string
let challenge: BKDFChallenge
getCachedEntry("selfImprovementChallenge", {
slug: slug,
}).then((res) => {
challenge = res
})
let activeTab = 1
</script>
<div class="rows-detailed-challenge">
<section class="challenge-detailed-preview">
{#if challenge}
<h2>Weekly Challenge #{getCalendarWeekFromDate(challenge.activeAt)}</h2>
<div class="preview-img-wrapper">
<MedialibImage id="{challenge.images.preview}" />
</div>
<h1>{challenge.title}</h1>
{/if}
</section>
<BlogTabSwitch
tabs="{['Einleitung', 'Informationen']}"
bind:selectedTab="{activeTab}"
/>
{#if activeTab == 0}
<section class="introduction">
{#if challenge}
{#each challenge.introduction as intro}
<p>{@html intro}</p>
{/each}
{/if}
</section>
{#if challenge}
<section class="invitation">
{challenge?.howItWorks?.invitation}
</section>
<Steps block="{challenge?.howItWorks}" />
{/if}
{:else if challenge}
<ChallengeBlog
entryId="{challenge.blog.blogId}"
thumbnail="{challenge.blog.thumbnail}"
sources="{challenge.blog.sources}"
/>
{/if}
<div style="padding: 3.6rem 0px;"></div>
</div>
<style
lang="less"
global
>
.rows-detailed-challenge {
max-width: var(--normal-max-width);
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 2.4rem;
.challenge-detailed-preview {
display: flex;
width: 100%;
flex-direction: column;
gap: 1.2rem;
max-width: var(--normal-max-width);
h2 {
color: transparent;
position: relative;
display: inline-block;
-webkit-text-stroke: 1px var(--krass-kraft-primary);
padding: 1.2rem 0px;
line-height: 4.8rem;
font-size: 4.8rem;
font-family: sans-serif;
}
.preview-img-wrapper {
width: 100%;
height: 518px;
img {
width: 100%;
height: 100%;
object-fit: cover;
position: relative;
z-index: 2;
}
}
span {
color: var(--text-300);
}
h1 {
font-size: 3.6rem;
line-height: 3.6rem;
font-weight: 700;
color: var(--text-100);
}
}
.introduction {
display: flex;
& > p {
padding: 0px 1.2rem;
color: var(--text-100);
border-right: 1px solid white;
* {
color: var(--text-100);
}
&:first-child {
padding-left: 0px;
}
&:last-child {
border-right: none;
padding-right: 0px;
}
}
}
.invitation {
padding: 2.4rem;
color: white;
border: 1px solid white;
}
}
</style>

View File

@@ -1,70 +0,0 @@
<script lang="ts">
import { spaLink } from "../../actions"
import MedialibImage from "../widgets/MedialibImage.svelte"
export let challenge: BKDFChallenge
</script>
<li class="challenge-preview-item">
<a
class="thumbnail-wrapper"
href="/selfimprovement/krasskraft/challenge/{challenge.slug}"
use:spaLink
>
<MedialibImage id="{challenge.images.preview}" />
</a>
<div class="challenge-footer">
<!-- <span>
KW {getCalendarWeekFromDate(challenge.activeAt)}
</span>-->
<h3>
{challenge.title}
</h3>
</div>
</li>
<style
lang="less"
global
>
@import "../../assets/css/variables.less";
li.challenge-preview-item {
max-width: 27rem;
width: 100%;
@media @mobile {
width: calc(100vw - 2 * var(--horizontal-default-margin) - 2px);
}
display: flex;
flex-direction: column;
gap: 1.2rem;
a.thumbnail-wrapper {
width: 100%;
aspect-ratio: 1.2 / 1;
display: flex;
position: relative;
img {
width: 100%;
height: 100%;
object-fit: cover;
position: relative;
z-index: 2;
}
}
.challenge-footer {
display: flex;
flex-direction: column;
gap: 1.2rem;
padding-bottom: 1.2rem;
border-bottom: 1px solid var(--krass-kraft-primary);
span {
color: var(--text-300);
}
h3 {
font-size: 1.6rem;
font-weight: 700;
color: var(--text-100);
}
}
}
</style>

View File

@@ -1,67 +0,0 @@
<script
lang="ts"
context="module"
>
import "simplebar"
import "simplebar/dist/simplebar.css"
import ResizeObserver from "resize-observer-polyfill"
if (typeof window !== "undefined") window.ResizeObserver = ResizeObserver
</script>
<script lang="ts">
import ChallengePreview from "./ChallengePreview.svelte"
export let challenges: BKDFChallenge[] = []
</script>
<div
class="product-preview-list verticalScrollbar"
data-simplebar
>
<ul class="inner-wrapper">
{#each challenges as challenge}
<ChallengePreview challenge="{challenge}" />
{/each}
</ul>
</div>
<style
lang="less"
global
>
.product-preview-list {
width: 100%;
max-width: var(--normal-max-width);
.simplebar-wrapper {
padding-bottom: 1.2rem;
.simplebar-content {
max-width: var(--normal-max-width);
& > .inner-wrapper {
display: flex;
gap: 2.2rem;
width: 100%;
}
}
}
.simplebar-track {
background-color: rgba(13, 12, 12, 0.25);
height: 7px;
overflow: visible;
margin-bottom: 5px;
}
.simplebar-scrollbar {
transition-duration: 0ms !important;
cursor: pointer;
&::before {
background-color: var(--bg-100);
top: -2px;
opacity: 1;
border-radius: 0;
height: 11px;
left: 0px;
transition-delay: 0s;
}
}
}
</style>

View File

@@ -1,99 +0,0 @@
<script lang="ts">
import { getDBEntries } from "../../../api"
import { spaLink } from "../../actions"
import Columns from "../pagebuilder/blocks/Columns.svelte"
import ChallengePreviewList from "./ChallengePreviewList.svelte"
export let chapter: SelfImprovementChapter
let challenges: BKDFChallenge[] = []
getDBEntries("selfImprovementChallenge", {
type: 1,
}).then((res) => {
challenges = res
console.log(challenges)
})
</script>
<section class="row hp-row">
<Columns
block="{{
type: 'columns',
columns: [
{
type: 'chapterDescription',
verticalAlign: 'middle',
chapterDescription: {
title: chapter.title,
description: chapter.description,
type: chapter.type,
},
},
{
type: 'image',
images: [chapter.previewImage],
verticalAlign: 'middle',
imageMobileBackground: true,
},
],
}}"
/>
</section>
<section class="challenges-row">
<div class="headline">
<div class="headline-col">
<h2>Challenges</h2>
</div>
<button class="">
<a
href="/selfimprovement/krasskraft/challenges"
use:spaLink
>
Alle Anzeigen
</a>
</button>
</div>
<ChallengePreviewList challenges="{challenges}" />
</section>
<style lang="less">
section {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
max-width: var(--normal-max-width);
}
.row {
flex-direction: row;
}
.hp-row {
height: calc(38rem + 145px + 96px + 96px);
max-height: 97vh;
}
.headline {
width: 100%;
margin-bottom: 1.2rem;
display: flex;
justify-content: space-between;
align-items: flex-end;
h2 {
font-size: 4.8rem;
font-weight: 500;
color: transparent;
font-family: sans-serif;
position: relative;
display: inline-block;
-webkit-text-stroke: 2px var(--krass-kraft-primary);
}
button {
a {
color: var(--krass-kraft-primary);
}
}
}
.challenges-row {
margin-bottom: 3.6rem;
}
</style>

View File

@@ -1,105 +0,0 @@
<script lang="ts">
import { onMount } from "svelte"
import Item from "./Item.svelte"
import { selfImprovementChapters } from "../../../../store"
export let block: ContentBlock<"selfImprovementChapterPreview">
const chapters = block.selfImprovementChapterPreview
let interval: NodeJS.Timeout
function startInterval() {
interval = setInterval(() => {
selectedChapter = (selectedChapter + 1) % $selfImprovementChapters.length
}, 3000)
}
function stopInterval() {
clearInterval(interval)
}
onMount(() => {
startInterval()
return () => clearInterval(interval)
})
let selectedChapter = 0
</script>
<div class="container">
<ul>
{#each $selfImprovementChapters as chapter, i}
<li
on:mouseenter="{() => {
stopInterval()
selectedChapter = i
}}"
on:mouseleave="{startInterval}"
class:active="{selectedChapter === i}"
>
<Item
chapter="{chapter}"
bind:selectedChapter="{selectedChapter}"
index="{i}"
previewImage="{chapters.find((p) => p.chapter === chapter.id)?.previewImage}"
/>
</li>
{/each}
</ul>
</div>
<style lang="less">
* {
transition-duration: 0s;
}
.container {
width: 100%;
}
ul {
padding: 0;
margin: 0;
list-style: none;
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr;
}
li {
width: 100%;
aspect-ratio: 4 / 3;
@media (max-width: 1500px) {
aspect-ratio: unset;
&::before {
float: left;
padding-top: 125%;
content: "";
}
&::after {
display: block;
content: "";
clear: both;
}
}
}
@media (max-width: 1500px) {
.container {
display: flex;
flex-direction: column;
margin-bottom: 2rem;
}
ul {
display: flex;
flex-wrap: wrap;
}
li {
width: 33.33%;
}
li.active {
order: -1;
width: 100%;
}
}
</style>

View File

@@ -1,350 +0,0 @@
<script lang="ts">
import { onMount, afterUpdate } from "svelte"
import MedialibImage from "../../../widgets/MedialibImage.svelte"
import { spaLink } from "../../../../actions"
import { getVariableNameForChapter } from "../../../../utils"
export let selectedChapter: number, index: number, chapter: SelfImprovementChapter, previewImage: string
const color = chapter.color
$: active = selectedChapter === index
let upperPart: HTMLElement
let lowerPart: HTMLElement
let topOverlay: HTMLElement
let bottomOverlay: HTMLElement
function updateOverlayHeights() {
if (topOverlay && bottomOverlay && upperPart && lowerPart) {
topOverlay.style.height = `${upperPart.offsetHeight + 24}px`
bottomOverlay.style.height = `${lowerPart.offsetHeight + 24}px`
}
}
onMount(() => {
window.addEventListener("resize", updateOverlayHeights)
})
afterUpdate(() => {
updateOverlayHeights()
})
$: if (active) {
updateOverlayHeights()
} else {
if (topOverlay && bottomOverlay) {
topOverlay.style.height = "0px"
bottomOverlay.style.height = "0px"
}
}
function getSlug(type: number) {
if (type == 1) return "krasskraft"
return "unknown"
}
let colorName = getVariableNameForChapter(chapter.type)
</script>
<div
class="chapter-preview-block"
class:active="{active}"
>
{#if chapter.locked}
<div
class="locked"
class:active="{active}"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 72 72"
fill="none"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M15.75 30.165V24C15.75 18.6294 17.8835 13.4787 21.6811 9.68109C25.4787 5.88348 30.6294 3.75 36 3.75C41.3706 3.75 46.5213 5.88348 50.3189 9.68109C54.1165 13.4787 56.25 18.6294 56.25 24V30.165C59.595 30.414 61.77 31.044 63.363 32.637C66 35.271 66 39.516 66 48C66 56.484 66 60.729 63.363 63.363C60.729 66 56.484 66 48 66H24C15.516 66 11.271 66 8.637 63.363C6 60.729 6 56.484 6 48C6 39.516 6 35.271 8.637 32.637C10.227 31.044 12.405 30.414 15.75 30.165ZM20.25 24C20.25 19.8228 21.9094 15.8168 24.8631 12.8631C27.8168 9.90937 31.8228 8.25 36 8.25C40.1772 8.25 44.1832 9.90937 47.1369 12.8631C50.0906 15.8168 51.75 19.8228 51.75 24V30.012C50.601 30 49.353 30 48 30H24C22.644 30 21.399 30 20.25 30.012V24ZM42 48C42 49.5913 41.3679 51.1174 40.2426 52.2426C39.1174 53.3679 37.5913 54 36 54C34.4087 54 32.8826 53.3679 31.7574 52.2426C30.6321 51.1174 30 49.5913 30 48C30 46.4087 30.6321 44.8826 31.7574 43.7574C32.8826 42.6321 34.4087 42 36 42C37.5913 42 39.1174 42.6321 40.2426 43.7574C41.3679 44.8826 42 46.4087 42 48Z"
fill="white"></path>
</svg>
<p>Coming Soon</p>
</div>
{/if}
<div class="background-image">
<MedialibImage id="{previewImage}" />
<div
bind:this="{topOverlay}"
class="overlay top"
style="background-color: var({colorName})"
></div>
<div
bind:this="{bottomOverlay}"
class="overlay bottom"
style="background-color: var({colorName})"
></div>
<div
class="overlay left"
style="background-color: var({colorName})"
></div>
<div
class="overlay right"
style="background-color: var({colorName})"
></div>
</div>
<div
class="content"
class:active="{active}"
>
<div
bind:this="{upperPart}"
class:active="{active}"
class="upper-part"
>
<h4
class:active="{active}"
style="{!active ? 'color: ' + color : 'color: var(--bg-100)'}"
>
{chapter.alias}
</h4>
<h3
class:active="{active}"
style="{!active ? 'color: ' + color : 'color: var(--bg-100)'}"
>
{chapter.title}
</h3>
<p
class:active="{active}"
style="{!active ? 'color: ' + 'transparent' : 'color: var(--bg-100)'}"
>
{chapter.shortDescription}
</p>
</div>
<div
bind:this="{lowerPart}"
class="lower-part"
class:active="{active}"
>
<button
style="background-color: var({colorName})"
disabled="{chapter.locked}"
>
<a
href="/selfimprovement/{getSlug(chapter.type)}"
use:spaLink
aria-disabled="{chapter.locked ? 'true' : 'false'}"
>
Weiter
</a>
</button>
</div>
</div>
</div>
<style
lang="less"
global
>
@import "../../../../assets/css/variables.less";
.chapter-preview-block {
position: relative;
height: 100%;
overflow: hidden;
.locked {
position: absolute;
z-index: 100;
pointer-events: none;
background: rgba(13, 12, 12, 0.5);
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 0.5rem;
p {
color: white;
text-transform: uppercase;
}
svg {
width: 72px;
height: 72px;
}
@media (max-width: 1500px) {
&:not(.active) {
svg {
height: 24px;
width: 24px;
}
p {
font-size: 0.6rem;
}
}
}
}
.background-image {
width: 100%;
height: 100%;
position: absolute;
z-index: 0;
img {
width: 100%;
height: 100%;
object-fit: cover;
}
.overlay {
position: absolute;
transition: all 0.3s ease;
&.top {
top: 0;
left: 0;
right: 0;
height: 0;
}
&.bottom {
bottom: 0;
left: 0;
right: 0;
height: 0;
}
&.left {
top: 0;
bottom: 0;
left: 0;
width: 0;
}
&.right {
top: 0;
bottom: 0;
right: 0;
width: 0;
}
}
}
&.active .background-image .overlay.top {
height: auto;
}
&.active .background-image .overlay.bottom {
height: auto;
}
&.active .background-image .overlay.left {
width: 3.6rem;
@media (max-width: 600px) {
width: 0.6rem;
}
}
&.active .background-image .overlay.right {
width: 3.6rem;
@media (max-width: 600px) {
width: 0.6rem;
}
}
.content {
transition: border-color 0.3s ease, color 0.3s ease;
border-top: 2.4rem solid transparent;
border-bottom: 2.4rem solid transparent;
border-left: 3.6rem solid transparent;
border-right: 3.6rem solid transparent;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
z-index: 10;
.upper-part {
transition: border-color 0.3s ease, color 0.3s ease, background-color 0.3s ease;
position: relative;
display: flex;
flex-direction: column;
gap: 1.2rem;
h4 {
margin: 0px;
text-transform: uppercase;
font-size: 1.2rem;
color: var(--bg-100);
}
h3 {
font-size: 3rem;
font-weight: 700;
margin-bottom: 1.2rem;
color: var(--bg-100);
}
p {
font-size: 1.2rem;
color: var(--bg-100);
}
@media (max-width: 1500px) {
h3:not(.active) {
font-size: 1.6rem;
}
p:not(.active) {
font-size: 1rem;
}
h4:not(.active) {
font-size: 1rem;
}
}
@media (max-width: 600px) {
gap: 0.6rem;
h3 {
font-size: 1.6rem;
margin-bottom: 0px;
}
h3:not(.active) {
font-size: 1rem;
}
p {
margin-top: 0px;
font-size: 0.8rem;
}
p:not(.active) {
font-size: 0.5rem;
}
h4 {
font-size: 0.8rem;
}
h4:not(.active) {
font-size: 0.5rem;
}
}
border-bottom: 2.4rem solid transparent;
@media (max-width: 600px) {
border-width: 0.6rem !important;
}
}
@media (max-width: 600px) {
border-width: 0.6rem !important;
}
.lower-part {
border-top: 2.4rem solid transparent;
@media (max-width: 600px) {
border-width: 0rem !important;
}
transition: border-color 0.3s ease, color 0.3s ease, background-color 0.3s ease;
button {
font-weight: 700;
font-size: 1.2rem;
text-transform: uppercase;
padding: 6px 12px;
@media (max-width: 1500px) {
padding-left: 0px;
}
color: var(--bg-100);
&[disabled] {
cursor: not-allowed;
pointer-events: none;
}
}
}
&:not(.active) {
@media (max-width: 1500px) {
.lower-part {
button {
display: none;
}
}
}
}
}
}
</style>

View File

@@ -1,261 +0,0 @@
<script
lang="ts"
context="module"
>
import "simplebar"
import "simplebar/dist/simplebar.css"
import ResizeObserver from "resize-observer-polyfill"
if (typeof window !== "undefined") window.ResizeObserver = ResizeObserver
</script>
<script lang="ts">
import { getCachedEntries } from "../../../../../api"
import { getBCGraphProductsByIds } from "../../../../functions/CommerceAPIs/bigCommerce/product"
import { backgroundImages } from "../../../../store"
import MedialibImage from "../../../widgets/MedialibImage.svelte"
export let block: ContentBlock<"ratingPreview">
let ratings: ProductRating[] = []
let productsMap: Record<string, BKDFProduct> = {}
getCachedEntries("rating", {
_id: {
$in: block?.ratingsPreview?.ratings?.map((rating) => rating.rating),
},
}).then((entries) => {
getBCGraphProductsByIds(entries.map((entry) => String(entry.bigCommerceProductId))).then((products) => {
productsMap = products.reduce((acc, product) => {
acc[product.id] = product
return acc
}, {})
ratings = entries
})
})
function formatDate(date: string) {
return new Date(date).toLocaleDateString("de-DE", {
year: "numeric",
month: "long",
day: "numeric",
})
}
function returnWidthForRating(rating: ProductRating) {
const ratingP =
(rating.rating.quality + rating.rating.priceQualityRatio + rating.rating.comfort + rating.rating.overall) *
5
return ratingP
}
function returnAvgForRating(rating: ProductRating) {
const ratingP =
(rating.rating.quality + rating.rating.priceQualityRatio + rating.rating.comfort + rating.rating.overall) /
4
return ratingP.toFixed(1)
}
</script>
{#if ratings.length}
<div
data-simplebar
class="review-list-container horizontalScrollbar"
>
<ul class="review-list">
{#each ratings as rating}
<li class="review-item">
<div class="upper-box">
<svg
xmlns="http://www.w3.org/2000/svg"
width="48"
height="49"
viewBox="0 0 48 49"
fill="none"
>
<path
d="M48 0.25V47.5833L0 0.25H48Z"
fill="white"></path>
</svg>
<div class="background-image">
<img
src="{productsMap[rating.bigCommerceProductId].featuredImage.url}"
alt="{rating.title}"
/>
<MedialibImage
id="{$backgroundImages?.standard}"
filter="l"
/>
</div>
<div class="overlay">
<h3>
{rating.title}
</h3>
<p>{rating.comment}</p>
<div class="date">{formatDate(rating.review_date)}</div>
</div>
</div>
<div class="rating-bar">
<div
class="rating-filled"
style="width: {returnWidthForRating(rating)}%; "
>
<div class="star-wrapper">
<svg
xmlns="http://www.w3.org/2000/svg"
width="25"
height="25"
viewBox="0 0 25 25"
fill="none"
>
<path
d="M16.7692 23.1373L16.7717 23.1388C17.1926 23.3925 17.6791 23.5165 18.1701 23.4952C18.6612 23.474 19.1351 23.3085 19.5326 23.0193C19.9301 22.7302 20.2335 22.3303 20.405 21.8697C20.5762 21.4098 20.6083 20.9097 20.4972 20.4318C20.497 20.431 20.4968 20.4302 20.4967 20.4294L19.3653 15.5223L22.8594 12.4729H22.8607L23.1422 12.2302C23.515 11.9087 23.7846 11.4842 23.9171 11.0101C24.0496 10.536 24.0392 10.0333 23.8872 9.56506C23.7352 9.09682 23.4483 8.68389 23.0626 8.37804C22.6772 8.0725 22.2103 7.88743 21.7202 7.84595C21.7197 7.84591 21.7192 7.84587 21.7187 7.84583L16.7503 7.41534L14.8029 2.78442C14.8027 2.78387 14.8024 2.78331 14.8022 2.78276C14.6125 2.32902 14.2929 1.94145 13.8836 1.66874C13.4738 1.39569 12.9924 1.25 12.5 1.25C12.0076 1.25 11.5262 1.3957 11.1164 1.66874C10.7069 1.94157 10.3872 2.32937 10.1976 2.78337C10.1974 2.78372 10.1973 2.78407 10.1971 2.78442L8.25556 7.41539L3.28596 7.84583C3.28549 7.84586 3.28502 7.8459 3.28455 7.84594C2.7945 7.8874 2.32754 8.07248 1.94214 8.37804C1.55638 8.68388 1.2695 9.09682 1.11748 9.56506C0.965459 10.0333 0.955069 10.536 1.08761 11.0101L2.05069 10.7409L1.08761 11.0101C1.21983 11.4831 1.48842 11.9066 1.85981 12.2279L5.63577 15.5275L4.50617 20.4294C4.50605 20.43 4.50592 20.4305 4.50579 20.4311C4.39454 20.9092 4.42654 21.4096 4.59781 21.8697C4.76928 22.3303 5.07273 22.7302 5.47023 23.0193C5.86773 23.3085 6.34163 23.474 6.8327 23.4952C7.32376 23.5165 7.81019 23.3925 8.23118 23.1388L8.23442 23.1368L12.4968 20.546L16.7692 23.1373Z"
fill="#2F4858"
stroke="white"
stroke-width="2"></path>
</svg>
<span>{returnAvgForRating(rating)}</span>
</div>
</div>
</div>
</li>
{/each}
</ul>
</div>
{/if}
<style
lang="less"
global
>
.review-list-container {
width: 100%;
max-width: var(--normal-max-width);
.simplebar-wrapper {
padding-bottom: 1.2rem;
.simplebar-content {
max-width: var(--normal-max-width);
& > .inner-wrapper {
display: flex;
gap: 2.2rem;
width: 100%;
}
}
}
.simplebar-track {
background-color: rgba(13, 12, 12, 0.25);
height: 7px;
overflow: visible;
margin-bottom: 5px;
}
.simplebar-scrollbar {
transition-duration: 0ms !important;
cursor: pointer;
&::before {
background-color: var(--bg-100);
top: -2px;
opacity: 1;
border-radius: 0;
height: 11px;
left: 0px;
transition-delay: 0s;
}
}
.review-list {
display: flex;
gap: 1.2rem;
height: 400px;
align-items: flex-start;
.review-item {
overflow: visible;
position: relative;
display: flex;
flex-direction: column;
gap: 1px;
.upper-box {
width: 320px;
height: 320px;
position: relative;
background: linear-gradient(180deg, rgba(0, 0, 0, 0) 0%, #000100);
svg {
position: absolute;
top: 0px;
right: 0px;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
position: absolute;
&:first-of-type {
z-index: -1;
}
&:last-of-type {
z-index: -2;
}
}
.overlay {
position: absolute;
bottom: 0;
padding: 1.2rem;
display: flex;
flex-direction: column;
gap: 0.6rem;
h3 {
color: var(--neutral-white);
font-family: Outfit;
font-size: 1rem;
font-style: normal;
font-weight: 700;
line-height: normal;
}
p {
color: var(--neutral-white);
font-family: Poly;
font-size: 1rem;
font-style: italic;
font-weight: 400;
line-height: normal;
}
.date {
color: var(--neutral-white);
border-top: 1px solid var(--neutral-white);
font-size: 0.7rem;
padding-top: 6px;
width: fit-content;
}
}
}
.rating-bar {
width: 100%;
height: 9px;
background: rgba(47, 72, 88, 0.2);
.rating-filled {
background: var(--text-invers-100);
height: 100%;
left: 0px;
position: relative;
.star-wrapper {
display: flex;
align-items: center;
flex-direction: column;
gap: 0.6rem;
padding: 0spx;
position: absolute;
top: 100%;
right: 0%;
transform: translate(50%, -35%);
span {
font-family: Outfit;
font-size: 20px;
font-style: normal;
font-weight: 700;
line-height: 14px;
margin-top: -4px;
}
}
}
}
}
}
}
</style>

View File

@@ -1,70 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from "svelte"
import { deleteCartItem, updateCartItem } from "../../../functions/CommerceAPIs/bigCommerce/cart"
import { deleteCookie } from "../../../functions/utils"
import ProductInCartPreview from "../product/ProductInCartPreview.svelte"
import LoadingWrapper from "../../widgets/LoadingWrapper.svelte"
const dispatcher = createEventDispatcher()
async function removeItem(id: string) {
if (cart.lines.length == 1) {
deleteCookie("cartId")
await deleteCartItem(cart.id, id, true)
dispatcher("removeCart")
} else cart = await deleteCartItem(cart.id, id)
}
interface UpdateItemQuantity {
merchandiseId: string
productId: string
quantity: number
entityId: string
}
async function updateItemQuantity(line: UpdateItemQuantity) {
cart = await updateCartItem(cart.id, line)
}
export let cart: BKDFCart,
hideActions = false,
showQuantity: boolean = false
let loading = -1
</script>
<ul class="products">
{#each cart.lines as item, i (item?.id)}
<li class="product">
<LoadingWrapper
active="{loading == i}"
styles=" display: flex; width: 100%;flex-direction: column; gap: 24px;"
>
<ProductInCartPreview
item="{item}"
hideActions="{hideActions}"
showQuantity="{showQuantity}"
on:updateQuantity="{(e) => {
loading = i
updateItemQuantity({
merchandiseId: item.merchandise.id,
quantity: e.detail.quantity,
productId: item.merchandise.product.id,
entityId: item.id,
}).finally(() => (loading = -1))
}}"
on:remove="{() => {
loading = i
removeItem(item.id).finally(() => (loading = -1))
}}"
/></LoadingWrapper
>
</li>
{/each}
</ul>
<style lang="less">
.products {
display: flex;
width: 100%;
flex-direction: column;
gap: 1.2rem;
.product {
width: 100%;
}
}
</style>

View File

@@ -1,200 +0,0 @@
<script
lang="ts"
context="module"
>
import "simplebar"
import "simplebar/dist/simplebar.css"
import ResizeObserver from "resize-observer-polyfill"
if (typeof window !== "undefined") window.ResizeObserver = ResizeObserver
</script>
<script lang="ts">
import { mdiCartCheck } from "@mdi/js"
import { getCart } from "../../../functions/CommerceAPIs/bigCommerce/cart"
import { deleteCookie, getCookie } from "../../../functions/utils"
import Icon from "../../widgets/Icon.svelte"
import CartProducts from "./CartProducts.svelte"
import Loader from "../Loader.svelte"
import { newNotification } from "../../../store"
import { minimumForFreeShipping } from "../../../../config"
const cartId = getCookie("cartId")
export let cart: BKDFCart | null = null,
hideActions = false,
showQuantity: boolean = false,
shippingIncludedInTotal = false
let loading = false
async function setCart(id: string) {
loading = true
cart = await getCart(id).catch(() => {
loading = false
newNotification({
class: "error",
html: `Fehler beim Laden des Warenkorbs. Bitte laden Sie die Seite neu.`,
})
deleteCookie("cartId")
})
loading = false
}
async function refetchCartAndGoToCheckout() {
loading = true
cart = await getCart(cartId).catch(() => {
loading = false
newNotification({
class: "error",
html: `Fehler beim Laden des Warenkorbs. Bitte laden Sie die Seite neu.`,
})
deleteCookie("cartId")
})
loading = false
if (cart) {
window.location.href = cart.checkoutUrl
}
}
if (cartId && !cart) setCart(cartId)
</script>
{#if cart}
<div
class="product-listing-wrapper"
data-simplebar
>
<section class="product-listing-inner">
<CartProducts
bind:cart="{cart}"
on:removeCart="{() => (cart = null)}"
hideActions="{hideActions}"
showQuantity="{showQuantity}"
/>
</section>
</div>
<section
class="cart-summary"
style="{cart.checkoutUrl ? '' : 'height: unset'} "
>
{#if Number(cart.cost.discountedAmount.amount) > 0}
<div class="discount">
<div>Rabatt</div>
<div>-{cart.cost.discountedAmount.amount}</div>
</div>
{/if}
{#if Number(cart?.cost?.couponDiscount?.amount || 0) > 0}
<div class="discount">
<div>Rabatt Codes</div>
<div>-{cart.cost.couponDiscount.amount}</div>
</div>
{/if}
<div class="discount">
<div>Versand</div>
<div>
{#if Number(cart.cost.amount.amount) >= minimumForFreeShipping}
<span>Kostenfrei</span>
{:else}
<span>5,00 €</span>
{/if}
</div>
</div>
<div class="total">
<em>Gesamt</em>
<div>
{(
Number(cart.cost.amount.amount) +
(Number(cart.cost.amount.amount) >= minimumForFreeShipping || shippingIncludedInTotal ? 0 : 5)
).toFixed(2)}
</div>
</div>
{#if cart.checkoutUrl}
<button
class="checkout"
on:click="{() => refetchCartAndGoToCheckout()}"
><Icon path="{mdiCartCheck}" /> <span>Zur Kasse</span></button
>
{/if}
</section>
{:else if loading}
<Loader size="3" />
{:else}
<p class="no-products">Keine Produkte im Warenkorb</p>
{/if}
<style lang="less">
@import "../../../assets/css/variables.less";
.no-products {
padding-left: 90px;
@media @mobile {
padding-left: 38px;
}
}
.product-listing-wrapper {
width: 100%;
max-height: calc(100% - 255px);
@media @mobile {
max-height: calc(100% - 200px);
}
flex-grow: 1;
.product-listing-inner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 24px 1rem 24px 90px;
@media @mobile {
padding: 24px 2.8rem 24px 38px;
}
}
}
.cart-summary {
display: flex;
flex-direction: column;
padding: 24px 1rem 24px 90px;
@media @mobile {
padding: 24px 2rem 24px 38px;
}
border-top: 1px solid var(--text-300);
height: 255px;
@media @mobile {
height: 200px;
padding-top: 1.2rem;
padding-bottom: 1.2rem;
}
.discount,
.total {
padding: 0.6rem 1.2rem;
width: 100%;
display: flex;
justify-content: space-between;
}
.discount {
border-bottom: 1px solid var(--text-300);
}
.total {
background-color: var(--bg-100);
color: var(--neutral-white);
em {
font-weight: 700;
font-size: 1rem;
line-height: 0.7rem;
}
}
.checkout {
width: 100%;
padding: 0.6rem 1.2rem;
font-weight: 700;
color: var(--neutral-white);
font-size: 1rem;
line-height: 0.7rem;
background-color: var(--primary-100);
box-shadow: 0px -2px 0px 0px rgba(0, 0, 0, 0.25);
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
margin: 35px 0px;
border-radius: 5px;
}
}
</style>

View File

@@ -1,86 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from "svelte"
import { getCart } from "../../../functions/CommerceAPIs/bigCommerce/cart"
import { getBCGraphProductsByCategory } from "../../../functions/CommerceAPIs/bigCommerce/product"
import { getCookie } from "../../../functions/utils"
import ProductPreview from "../product/ProductPreview.svelte"
const dispatch = createEventDispatcher()
const cartId = getCookie("cartId")
let products: BKDFProduct[] = []
async function loadProductRecommendations() {
const cart = await getCart(cartId).catch((error) => {
console.log(error)
})
if (cart) {
const categories = cart.lines.map((l) => {
return l.merchandise.product.categories[0].id
})
const productsOfCategories: BKDFProduct[] = []
const promises = categories.map(async (category) => {
const products = await getBCGraphProductsByCategory(category)
if (products.length > 0) {
products.forEach((p) => {
if (!productsOfCategories.find((product) => product.id === p.id)) {
productsOfCategories.push(p)
}
})
}
})
await Promise.all(promises)
if (productsOfCategories.length == 0) {
dispatch("removeOverlay")
return
}
// take 5 random indices of products not already in cart and every index only once and make sure at
let i = 0
const randomIndizes: number[] = []
while (randomIndizes.length < 5) {
i++
if (i > 100) {
break
}
const randomIndex = Math.floor(Math.random() * productsOfCategories.length)
if (
!randomIndizes.includes(randomIndex) &&
!cart.lines.find((line) => line.merchandise.product.id === productsOfCategories[randomIndex].id)
) {
randomIndizes.push(randomIndex)
}
}
products = randomIndizes.map((index) => productsOfCategories[index])
dispatch("showOverlay")
} else dispatch("removeOverlay")
}
loadProductRecommendations()
</script>
{#if products.length > 0}
<div class="product-overlay-listing-wrapper">
<ul>
{#each products as product}
<ProductPreview
product="{product}"
brightVersion="{true}"
/>
{/each}
</ul>
</div>
{/if}
<style
lang="less"
global
>
.product-overlay-listing-wrapper {
li.product-preview {
width: 100% !important;
}
& > ul {
display: flex;
flex-direction: column;
gap: 2.4rem;
}
}
</style>

View File

@@ -1,60 +0,0 @@
<script lang="ts">
import { getBCGraphProductsByIds } from "../../../functions/CommerceAPIs/bigCommerce/product"
import { wishlist } from "../../../store"
import Loader from "../Loader.svelte"
import ProductInCartPreview from "../product/ProductInCartPreview.svelte"
async function loadProducts(): Promise<Partial<BKDFCartItem[]>> {
const products = await getBCGraphProductsByIds($wishlist.items.map((p) => String(p.product_id)))
const productsFormated: Partial<BKDFCartItem[]> = products.map((p) => {
const variant = p.variants.find(
(v) =>
Number(v.id) ==
Number($wishlist.items.find((w) => String(w.product_id) === String(p.id))?.variant_id)
)
return {
previewImage: p.featuredImage,
merchandise: {
id: variant.id,
selectedOptions: variant?.selectedOptions,
product: p,
},
}
})
return productsFormated
}
let reload = false
</script>
{#key $wishlist?.items?.length}
{#if !$wishlist?.items?.length}
<p>Keine Produkte in der Wunschliste</p>
{:else}
{#await loadProducts()}
<Loader size="3" />
{:then products}
{#if products.length == 0}
<p>Keine Produkte in der Wunschliste</p>
{:else}
<ul>
{#each products as product}
<li>
<ProductInCartPreview
item="{product}"
on:removedFavorite="{() => (reload = !reload)}"
hideActions="{true}"
showFavoriteActionButtons="{true}"
/>
</li>
{/each}
</ul>{/if}
{/await}
{/if}
{/key}
<style lang="less">
ul {
display: flex;
flex-direction: column;
gap: 1.2rem;
}
</style>

View File

@@ -1,44 +0,0 @@
<script lang="ts">
import { login } from "../../../store"
import Button from "../../widgets/Button.svelte"
import FavoriteListItems from "./FavoriteListItems.svelte"
</script>
<section class="wishlistWrapper">
{#if $login}
<FavoriteListItems />
{:else}
<p>Um deine Favoriten zu sehen, musst du eingeloggt sein.</p>
<div>
<Button
button="{{
ctaType: 0,
page: '/profile/login',
buttonTarget: '_self',
buttonText: 'Einloggen',
}}"
/>
<Button
button="{{
ctaType: 1,
page: '/profile/register',
buttonTarget: '_self',
buttonText: 'Registrieren',
}}"
/>
</div>
{/if}
</section>
<style lang="less">
@import "../../../assets/css/variables.less";
.wishlistWrapper {
display: flex;
flex-direction: column;
gap: 2.4rem;
padding: 24px 1rem 24px 90px;
@media @mobile {
padding: 24px 2rem 24px 38px;
}
}
</style>

View File

@@ -1,134 +0,0 @@
<script lang="ts">
import { getCachedEntries } from "../../../../api"
import { spaLink } from "../../../actions"
import MedialibImage from "../../widgets/MedialibImage.svelte"
import Loader from "../Loader.svelte"
import CardWrapper from "../profile/CardWrapper.svelte"
export let chapter: HelpCenterChapter
let questions: ContentEntry[]
let loading = true
getCachedEntries("content", { type: "helpcenterQuestion", path: { $in: chapter.questions.map((q) => q.page) } })
.then((res) => {
questions = res
})
.finally(() => (loading = false))
</script>
<CardWrapper>
<div
class="iconCardWrapper"
slot="header"
>
<div class="inner-wrapper">
<div class="blackBox"></div>
<MedialibImage id="{chapter.brightIcon}" />
</div>
</div>
<div class="innerWrapper">
<h3>{chapter.title}</h3>
<ul class="questions">
{#if loading}
<Loader size="3" />
{:else if questions}
{#each questions as question, i}
{#if i < 3}
<li class="question">
<a
href="/helpCenter/{chapter.slug}{question.path}"
use:spaLink
>
{question.question}
</a>
</li>
{/if}
{/each}
{:else}
<p>Es wurden keine Fragen gefunden.</p>
{/if}
</ul>
<div class="action-row">
<button class="cta secondary">
<a
href="/helpCenter/{chapter.slug}"
use:spaLink>weitere Fragen</a
></button
>
</div>
</div>
</CardWrapper>
<style
lang="less"
global
>
.iconCardWrapper {
position: absolute;
top: 0px;
left: 50%;
display: flex;
justify-content: center;
align-items: center;
transform: translateX(-50%) translateY(-50%);
width: 4.8rem;
height: 4.8rem;
.inner-wrapper {
position: relative;
display: flex;
justify-content: center;
align-items: center;
.blackBox {
position: absolute;
width: 2.4rem;
height: 2.4rem;
background-color: var(--bg-100);
transform: rotate(45deg);
}
img {
width: 1.6rem;
height: 1.6rem;
position: relative;
object-fit: contain;
z-index: 100;
}
}
}
.innerWrapper {
padding: 0px 2.4rem 2.4rem 2.4rem;
display: flex;
flex-direction: column;
height: 100%;
gap: 1.8rem;
align-items: center;
justify-content: center;
h3 {
font-size: 1.6rem;
line-height: 1.6rem;
font-weight: 700;
border-bottom: 2px solid var(--bg-100);
padding-bottom: 0.6rem;
}
ul {
display: flex !important;
flex-direction: column !important;
flex-grow: 1;
gap: 0.6rem !important;
width: 100%;
li {
padding: 0.6rem 1.2rem;
border-radius: 2px;
width: 100%;
border: 1px solid var(--bg-100);
a {
font-size: 1rem;
font-weight: 400;
font-family: Outfit;
}
}
}
.action-row {
width: 100%;
display: flex;
justify-content: flex-start;
}
}
</style>

View File

@@ -1,182 +0,0 @@
<script lang="ts">
import { mdiArrowLeft } from "@mdi/js"
import { spaLink } from "../../../actions"
import Icon from "../../widgets/Icon.svelte"
import { getHelpCenterChapters } from "../../../functions/CommerceAPIs/tibiEndpoints/helpCenter"
import Loader from "../Loader.svelte"
import MedialibImage from "../../widgets/MedialibImage.svelte"
import ChapterQuestionPreview from "./ChapterQuestionPreview.svelte"
import ChapterQuestionDetailed from "./ChapterQuestionDetailed.svelte"
export let location: LocationStore
$: pathBefore = location.path.split("/").slice(0, -1).join("/")
let chapters: HelpCenterChapter[]
getHelpCenterChapters().then((res) => {
chapters = res
})
</script>
<div class="chapterDetailed">
<div class="return">
<a
use:spaLink
href="{pathBefore}"><Icon path="{mdiArrowLeft}" /> <span>Zurück</span></a
>
</div>
<div class="main">
<ul class="nav">
{#if !Array.isArray(chapters)}
<Loader size="3" />
{:else}
{#each chapters as chapter}
<li class:active="{location.path.includes(`/helpCenter/${chapter.slug}`)}">
<a
href="/helpCenter/{chapter.slug}"
use:spaLink
>
<span class="icon">
<MedialibImage
id="{location.path.includes(`/helpCenter/${chapter.slug}`)
? chapter.brightIcon
: chapter.darkIcon}"
/>
</span>
{chapter.title}
</a>
</li>
{/each}
{/if}
</ul>
{#if pathBefore == "/helpCenter" || pathBefore == "/helpCenter/"}
<ul class="questions">
{#if !Array.isArray(chapters)}
<Loader size="3" />
{:else}
{#each chapters as chapter}
<ChapterQuestionPreview
chapter="{chapter}"
location="{location}"
/>
{/each}
{/if}
</ul>
{:else}
<ChapterQuestionDetailed location="{location}" />
{/if}
</div>
</div>
<style
lang="less"
global
>
@import "../../../../lib/assets/css/variables.less";
.chapterDetailed {
display: flex;
flex-direction: column;
gap: 2.4rem;
width: 100%;
max-width: 100%;
.return {
a {
display: flex;
gap: 6px;
align-items: center;
font-weight: 400;
font-family: Outfit;
}
}
.main {
display: flex;
gap: 2.4rem;
@media @mobile {
flex-direction: column;
}
.nav {
display: flex;
flex-direction: column;
gap: 10px;
min-width: 360px;
@media @mobile {
min-width: 100%;
}
li {
border-radius: 2px;
border: 1px solid var(--bg-100);
display: flex;
align-items: center;
gap: 1.2rem;
background-color: var(--neutral-white);
a {
display: flex;
width: 100%;
height: 100%;
padding: 0.6rem 1.2rem;
align-items: center;
gap: 1.2rem;
.icon {
min-height: 1.2rem;
min-width: 1.2rem;
height: 1.2rem;
width: 1.2rem;
img {
width: 100%;
height: 100%;
}
}
color: var(--bg-100);
}
&.active {
background: var(--bg-100);
a {
color: var(--neutral-white);
}
}
}
}
.questions {
display: flex;
flex-direction: column;
gap: 1.2rem;
flex-grow: 1;
.question {
display: flex;
justify-content: space-between;
width: 100%;
align-items: center;
gap: 1.2rem;
padding: 0.6rem 0px;
border-bottom: 1px solid var(--bg-100);
cursor: pointer;
a {
color: var(--bg-100);
font-weight: 400;
font-family: Outfit;
}
button {
width: 1.6rem;
height: 1.6rem;
min-width: 1.6rem;
min-height: 1.6rem;
border: 2px solid var(--bg-100);
background-color: var(--neutral-white);
transform: rotate(45deg);
color: var(--bg-100);
margin-bottom: 0.5rem;
}
&:hover {
button {
transform: rotate(0);
background-color: var(--bg-100);
color: var(--neutral-white);
}
}
}
}
}
}
</style>

View File

@@ -1,51 +0,0 @@
<script lang="ts">
import { getCachedEntry } from "../../../../api"
import NotFound from "../../../../routes/NotFound.svelte"
import ContentBlock from "../ContentBlock.svelte"
import Loader from "../Loader.svelte"
import Index from "../SEO/Index.svelte"
export let location: LocationStore
let question: ContentEntry
let loading = true
getCachedEntry("content", {
type: "helpcenterQuestion",
path: `/${location.path.split("/").filter(Boolean).pop()}`,
})
.then((res) => {
question = res
})
.finally(() => {
loading = false
})
</script>
{#if loading}
<Loader size="3" />
{:else if question}
<Index
title="{question.question} - BinKrassDuFass"
keywords="Hilfe, FAQ, Fragen, Antworten, BinKrassDuFass"
metaDescription="Antwort auf die Frage {question.question}."
article="{true}"
/>
<div class="blocks">
{#each question.blocks || [] as block, idx}
<ContentBlock
block="{block}"
noHorizontalMargin="{true}"
verticalPadding="{idx !== 0}"
/>
{/each}
</div>
{:else}
<NotFound />
{/if}
<style lang="less">
.blocks {
display: flex;
flex-direction: column;
gap: 1.2rem;
width: 100%;
}
</style>

View File

@@ -1,47 +0,0 @@
<script lang="ts">
import { mdiLinkVariant } from "@mdi/js"
import { getCachedEntries } from "../../../../api"
import { spaLink, spaNavigate } from "../../../actions"
import Icon from "../../widgets/Icon.svelte"
import Loader from "../Loader.svelte"
import Index from "../SEO/Index.svelte"
export let chapter: HelpCenterChapter, location: LocationStore
getCachedEntries("content", {
type: "helpcenterQuestion",
path: { $in: chapter.questions.map((q) => q.page) },
}).then((res) => {
questions = res
})
let questions: ContentEntry[]
</script>
{#if location.path.includes(`/helpCenter/${chapter.slug}`)}
<Index
title="{chapter.title} - BinKrassDuFass"
keywords="Hilfe, FAQ, Fragen, Antworten, BinKrassDuFass"
metaDescription="Häufig gestellte Fragen und Antworten zu BinKrassDuFass im Themengebiet {chapter.title}."
/>
{#if !Array.isArray(questions)}
<Loader size="3" />
{:else}
{#each questions as question}
<li class="question">
<a
href="/helpCenter/{chapter.slug}{question.path}"
use:spaLink
>
{question.question}
</a>
<button
aria-label="Link zur Frage"
on:click="{() => {
spaNavigate(`/helpCenter/${chapter.slug}${question.path}`)
}}"
>
<Icon path="{mdiLinkVariant}" />
</button>
</li>
{/each}
{/if}
{/if}

View File

@@ -1,89 +0,0 @@
<script lang="ts">
import { icons } from "../../../../config"
import { addProductToCart } from "../../../functions/helper/product"
import { newNotification } from "../../../store"
import Icon from "../../widgets/Icon.svelte"
import ToggleFavorite from "./ToggleFavorite.svelte"
import ProductQuantity from "./widgets/ProductQuantity.svelte"
export let variant: BKDFProductVariant,
possibleVariants: BKDFProductVariant[],
mobileFormat = false
let quantity = 1
function noVariantError() {
newNotification({
class: "error",
html: `Bitte wähle eine eindeutige Variante aus. Du musst sowohl eine Farbe als auch eine Größe auswählen.`,
})
}
function addToCart(variant: BKDFProductVariant, quantity: number) {
loading = true
addProductToCart(variant, quantity)
.then(() => {
loading = false
})
.catch(() => {
loading = false
})
}
let loading = false
</script>
<div
id="actionRow"
class="actionRow"
class:mobileFormat="{mobileFormat}"
>
<ProductQuantity bind:quantity="{quantity}" />
<button
disabled="{loading}"
id="addToCart"
class="cta primary"
on:click="{() => {
if (!variant) return noVariantError()
addToCart(variant, quantity)
}}"
>
<Icon
path="{icons.shoppingBag}"
color="#F3EED9"
width="{24}px"
height="{24}px"
props="{{ 'fill-rule': 'evenodd', 'clip-rule': 'evenodd' }}"
/>
IN DIE TASCHE</button
>
<ToggleFavorite
productId="{Number(!variant ? possibleVariants[0]?.parentId : variant?.parentId)}"
variantid="{Number(!variant ? possibleVariants[0]?.id : variant?.id)}"
bigFormat="{mobileFormat}"
/>
</div>
<style lang="less">
.actionRow {
display: grid;
gap: 12px;
margin-top: 1.2rem;
grid-template-columns: 1fr 8fr 1fr;
&.mobileFormat {
grid-template-columns: 1fr 3fr;
gap: 0;
// give thrid item full width not just 1 fr
}
#addToCart {
border-radius: 2px;
padding: 0.6rem 1.2rem 0.6rem 1.2rem;
flex-grow: 1;
font-weight: 700;
font-family: Outfit-Bold, sans-serif;
font-size: 1rem;
display: flex;
justify-content: center;
align-items: center;
gap: 10px;
}
}
</style>

View File

@@ -1,37 +0,0 @@
<script lang="ts">
import Icon from "../../widgets/Icon.svelte"
import { icons } from "../../../../config"
import { addProductToCart } from "../../../functions/helper/product"
export let productId, variantId: number
</script>
<button
aria-label="In den Warenkorb legen"
on:click="{() => {
addProductToCart(
{
id: variantId,
parentId: productId,
},
1
)
}}"
>
<Icon
path="{icons.shoppingBag}"
color="#2f4858"
width="{24}px"
height="{24}px"
props="{{ 'fill-rule': 'evenodd', 'clip-rule': 'evenodd' }}"
/>
</button>
<style lang="less">
button {
position: relative;
z-index: 100;
color: var(--text-invers-100);
}
</style>

View File

@@ -1,3 +0,0 @@
<script lang="ts">
export let CL: CompleteYourLook
</script>

View File

@@ -1,31 +0,0 @@
<script lang="ts">
import { getCachedEntries } from "../../../../api"
import ContentBlock from "../ContentBlock.svelte"
export let bigCommerceProductId: number
getCachedEntries("content", {
products: bigCommerceProductId,
type: "product",
}).then((res) => {
contentEntries = res
})
let contentEntries: ContentEntry
</script>
<div class="dRows">
{#each contentEntries || [] as contentEntry}
{#each contentEntry.blocks || [] as block, idx}
<ContentBlock
block="{block}"
noHorizontalMargin="{true}"
/>
{/each}
{/each}
</div>
<style
lang="less"
global
>
.dRows {
}
</style>

View File

@@ -1,71 +0,0 @@
<script lang="ts">
import { spaNavigate } from "../../../actions"
import { categories } from "../../../store"
import FilterBlock from "./widgets/FilterBlock.svelte"
function findCategoryByPath(categories: Category[], path: string): Category {
for (const category of categories) {
if (category.path === path) {
return category
}
const foundInChildren = findCategoryByPath(category.children, path)
if (foundInChildren) {
return foundInChildren
}
}
return undefined
}
function getDepthOfCategory(categories: Category[], path: string): number {
for (const category of categories) {
if (category.path === path) {
return 0
}
const foundInChildren = getDepthOfCategory(category.children, path)
if (foundInChildren !== undefined) {
return foundInChildren + 1
}
}
return undefined
}
export let path: string
const category = findCategoryByPath($categories, path)
</script>
{#if category}
<div class="filter-wrapper">
<FilterBlock
title="{category.name}"
deletable="{getDepthOfCategory($categories, path) > 0}"
active="{true}"
on:click="{() => {
if (getDepthOfCategory($categories, path) > 0) {
const newPath = path.endsWith('/') ? path.slice(0, -1) : path
spaNavigate(`/collections/${newPath.split('/').slice(0, -1).join('/')}`)
}
}}"
/>
{#each category.children as subcategory}
<FilterBlock
title="{subcategory.name}"
active="{false}"
on:click="{() => {
spaNavigate(`/collections${subcategory.path}`)
}}"
/>
{/each}
</div>
{/if}
<style lang="less">
.filter-wrapper {
display: flex;
max-width: 100%;
overflow-x: auto;
gap: 12px;
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
&::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
}
</style>

View File

@@ -1,141 +0,0 @@
<script lang="ts">
import { backgroundImages } from "../../../store"
import MedialibImage from "../../widgets/MedialibImage.svelte"
export let src: string, alt: string
let shouldZoom = false,
imagePosition: HTMLButtonElement,
zoomFactor = 3, // Example zoom factor, adjust for stronger or weaker zoom
initialClickPosition = { x: 0, y: 0 },
currentPosition = { x: 0, y: 0 },
coordX = 0,
coordY = 0,
lastTouchMoveEvent: number = 0
function handleMousemove(event: MouseEvent) {
if (!shouldZoom) return
updatePosition(event.clientX, event.clientY)
}
function handleTouchMove(event: TouchEvent) {
if (!shouldZoom) return
event.preventDefault()
const now = Date.now()
if (now - lastTouchMoveEvent < 10) return
lastTouchMoveEvent = now
updatePosition(event.touches[0].clientX, event.touches[0].clientY)
}
function handleClick(event: MouseEvent | TouchEvent) {
shouldZoom = !shouldZoom
if (shouldZoom) {
if (event instanceof MouseEvent) handleMousemove(event)
else if (event instanceof TouchEvent) handleTouchMove(event)
} else resetPosition()
}
function updatePosition(clientX: number, clientY: number) {
var rect = imagePosition.getBoundingClientRect()
currentPosition.x = clientX - rect.left
currentPosition.y = clientY - rect.top
coordX = currentPosition.x * -1 * zoomFactor
coordY = currentPosition.y * -1 * zoomFactor
}
function resetPosition() {
initialClickPosition.x = 0
initialClickPosition.y = 0
coordX = 0
coordY = 0
}
</script>
<div class="imageholder">
<button
class="image"
aria-label="zoom in image"
class:allowZoom="{shouldZoom}"
on:click="{handleClick}"
on:mousemove="{handleMousemove}"
on:touchmove="{handleTouchMove}"
bind:this="{imagePosition}"
>
<img
src="{src.replace('2000w', '800w')}"
alt="{alt}"
/>
<div class="zoomimg">
<img
src="{src}"
alt="{alt}"
style="left: {coordX}px; top: {coordY}px;"
/>
<div class="background-img-product">
<MedialibImage
id="{$backgroundImages['standard']}"
filter="l"
/>
</div>
</div>
<div class="background-img-product">
<MedialibImage
id="{$backgroundImages['standard']}"
filter="l"
/>
</div>
</button>
</div>
<style lang="less">
@import "../../../assets/css/variables.less";
.imageholder {
height: 100%;
width: fit-content;
flex-shrink: 0;
.image {
background: var(--bg-300);
height: 100%;
position: relative;
overflow: hidden;
img {
height: 100%;
width: auto;
object-fit: contain;
position: relative;
z-index: 2;
}
.zoomimg {
opacity: 0;
visibility: hidden;
background: var(--bg-300);
z-index: 2;
position: absolute;
top: 0;
left: 0px;
bottom: 0;
right: 0px;
img {
position: absolute;
width: 400%;
height: auto;
top: 0;
left: 0;
transition: top 0s ease, left 0s ease;
}
}
&:hover {
cursor: cell;
}
&.allowZoom:hover {
cursor: crosshair;
> .zoomimg {
opacity: 1;
visibility: visible;
}
}
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More