diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b9f215..7ba10d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Use pnpm uses: pnpm/action-setup@v2 @@ -22,9 +22,9 @@ jobs: version: 8 - name: Use Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 cache: 'pnpm' - name: Install dependencies diff --git a/CHANGELOG.md b/CHANGELOG.md index faf327d..9877860 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,32 @@ # Changelog +## v2401.0.0 + +*Released 07.02.2024* + +### Breaking Changes: + +- Updated `schema.json` to CoreMedia Content Cloud v12 - 2401.1 and to the latest CoreMedia Campaign Service changes +- Updated `node.js` to 20 LTS + +### Features: + +- Small Design Refresh + with new logo, white header, and animated image map hotzone icons. + Enhanced shoppable video, and product detail page and other small adjustments. +- Added localization and translations (i18n) +- Added shopping cart and checkout functionality + +### Bugfixes and Changes: + +- Updated minor versions of dependencies +- Fix standalone-fragment render error + +--- + ## v2310.0.0 -*Released 24.10.2023* +*Released 20.10.2023* ### Breaking Changes: diff --git a/Dockerfile b/Dockerfile index 9c60ccd..0830538 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,2 +1,2 @@ -FROM node:18 +FROM node:20 RUN npm install -g pnpm@8 diff --git a/README.md b/README.md index 46a96b0..1897f63 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ ![CoreMedia Labs Logo](https://documentation.coremedia.com/badges/banner_coremedia_labs_wide.png) -![CoreMedia Content Cloud Version](https://img.shields.io/static/v1?message=2310&label=CoreMedia%20Content%20Cloud&style=for-the-badge&labelColor=666666&color=672779 +![CoreMedia Content Cloud Version](https://img.shields.io/static/v1?message=2401&label=CoreMedia%20Content%20Cloud&style=for-the-badge&labelColor=666666&color=672779 "This badge shows the CoreMedia version this project is compatible with. Please read the versioning section of the project to see what other CoreMedia versions are supported and how to find them." ) @@ -50,7 +50,7 @@ Please refer to the [changelog](CHANGELOG.md) for more details. ## Quickstart -You need at least Node.js 18 (LTS), pnpm 7 and a running instance of the CoreMedia Content Cloud. +You need at least Node.js 20 (LTS), pnpm 8 and a running instance of the CoreMedia Content Cloud. Define your environment variables in `.env` file for the stitching server: [servers/stitching/.env](servers/stitching/.env.example) diff --git a/apps/spark/index.html b/apps/spark/index.html index bb46733..064db67 100644 --- a/apps/spark/index.html +++ b/apps/spark/index.html @@ -5,7 +5,7 @@ - + CoreMedia Spark diff --git a/apps/spark/package.json b/apps/spark/package.json index 3dda5a9..8b263da 100644 --- a/apps/spark/package.json +++ b/apps/spark/package.json @@ -17,62 +17,68 @@ "build-storybook": "storybook build -o dist/storybook" }, "dependencies": { - "@apollo/client": "^3.8.6", + "@apollo/client": "^3.9.3", "@coremedia-labs/graphql-layer": "workspace:^", "@coremedia-labs/preview-integration": "workspace:^", "@coremedia-labs/view-dispatcher": "workspace:^", "@js-joda/core": "^5.6.1", "@js-joda/timezone": "^2.18.2", + "country-flag-icons": "^1.5.9", "crypto-hash": "^2.0.1", "graphql": "^16.8.1", - "loglevel": "^1.8.1", + "i18next": "^23.8.2", + "i18next-browser-languagedetector": "^7.2.0", + "i18next-http-backend": "^2.4.3", + "loglevel": "^1.9.1", "query-string": "^7.1.3", "react": "^17.0.2", "react-dom": "^17.0.2", "react-helmet-async": "^1.3.0", + "react-i18next": "^11.18.6", "react-is": "^18.2.0", - "react-player": "^2.13.0", + "react-player": "^2.14.1", + "react-responsive": "^9.0.2", "react-router-dom": "^5.3.4", - "react-slick": "^0.29.0", + "react-slick": "^0.30.1", "require-from-string": "^2.0.2", "slick-carousel": "^1.8.1", "styled-components": "^5.3.11" }, "devDependencies": { - "@storybook/addon-actions": "^7.5.0", - "@storybook/addon-backgrounds": "^7.5.0", - "@storybook/addon-docs": "^7.5.0", - "@storybook/addon-essentials": "^7.5.0", - "@storybook/addon-links": "^7.5.0", - "@storybook/addon-measure": "^7.5.0", - "@storybook/addon-outline": "^7.5.0", - "@storybook/client-api": "^7.5.0", - "@storybook/client-logger": "^7.5.0", - "@storybook/node-logger": "^7.5.0", - "@storybook/react": "^7.5.0", - "@storybook/react-vite": "^7.5.0", - "@types/node": "^18.18.6", - "@types/react": "^17.0.69", - "@types/react-dom": "^17.0.22", + "@storybook/addon-actions": "^7.6.13", + "@storybook/addon-backgrounds": "^7.6.13", + "@storybook/addon-docs": "^7.6.13", + "@storybook/addon-essentials": "^7.6.13", + "@storybook/addon-links": "^7.6.13", + "@storybook/addon-measure": "^7.6.13", + "@storybook/addon-outline": "^7.6.13", + "@storybook/client-api": "^7.6.13", + "@storybook/client-logger": "^7.6.13", + "@storybook/node-logger": "^7.6.13", + "@storybook/react": "^7.6.13", + "@storybook/react-vite": "^7.6.13", + "@types/node": "^20.11.16", + "@types/react": "^17.0.75", + "@types/react-dom": "^17.0.25", "@types/react-router-dom": "^5.3.3", - "@types/react-slick": "^0.23.11", - "@types/styled-components": "^5.1.29", - "@typescript-eslint/eslint-plugin": "^6.8.0", - "@typescript-eslint/parser": "^6.8.0", - "@vitejs/plugin-react-swc": "^3.4.0", - "dotenv": "^16.3.1", - "eslint": "^8.51.0", - "eslint-config-prettier": "^9.0.0", + "@types/react-slick": "^0.23.13", + "@types/styled-components": "^5.1.34", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "@vitejs/plugin-react-swc": "^3.6.0", + "dotenv": "^16.4.1", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-react": "^7.33.2", "eslint-plugin-storybook": "^0.6.15", - "prettier": "^3.0.3", - "storybook": "^7.5.0", - "typedoc": "^0.25.2", + "prettier": "^3.2.5", + "storybook": "^7.6.13", + "typedoc": "^0.25.7", "typescript": "~5.2.2", - "vite": "^4.5.0", + "vite": "^4.5.2", "vitest": "^0.34.6" }, "browserslist": { diff --git a/apps/spark/public/favicon-apple-touch-icon.png b/apps/spark/public/favicon-apple-touch-icon.png new file mode 100644 index 0000000..db6fd31 Binary files /dev/null and b/apps/spark/public/favicon-apple-touch-icon.png differ diff --git a/apps/spark/public/favicon.ico b/apps/spark/public/favicon.ico index 869d231..57116da 100644 Binary files a/apps/spark/public/favicon.ico and b/apps/spark/public/favicon.ico differ diff --git a/apps/spark/public/locales/de/translation.json b/apps/spark/public/locales/de/translation.json new file mode 100644 index 0000000..539b465 --- /dev/null +++ b/apps/spark/public/locales/de/translation.json @@ -0,0 +1,137 @@ +{ + "Button": { + "defaultText": "Weitere Infos", + "addToCart" : "Jetzt kaufen", + "addMoreCart" : "Weitere kaufen", + "downloadText": "Herunterladen" + }, + "CartList": { + "emptyText": "Dein Warnkorb ist leer.", + "product": "Produkt:", + "quantity": "Anzahl:", + "price": "Preis:", + "description": "Beschreibung:" + }, + "DetailedCart": { + "headline": "Warenkorb", + "emptyText": "Dein Warenkorb ist leer.", + "description": "Beschreibung", + "price": "Preis", + "quantity": "Anzahl", + "total": "Summe", + "checkoutButtonText": "Zur Kasse", + "continueShopping": "Weiter einkaufen", + "cartTotals": "Warenkorb Summe", + "subtotalInclTax": "Zwischensumme (inkl. MwSt.)", + "tax": "MwSt." + }, + "HeaderCart": { + "title": "Warenkorb" + }, + "HeaderSearchForm": { + "close": "Suche schließen", + "label": "Suchen", + "placeholder": "Suchen ...", + "submit": "Suchen" + }, + "Loading": { + "loading": "laden ..." + }, + "SideCart": { + "title": "Warenkorb", + "close": "Schließen", + "openCartButtonText": "Warenkorb anzeigen" + }, + "Ticker": { + "title": "Eilmeldung" + }, + "FacetFilters": { + "title": "Filter" + }, + "ProductAssets": { + "downloads": "Downloads" + }, + "Search": { + "Header": { + "title": "Suchergebnisse", + "message": "Ihre Suche nach {{query}} ergab {{totalCount}} Treffer." + } + }, + "SearchFilter": { + "closeSearch": "Suche schließen", + "filters": "Filter", + "type_CMArticle": "Stories", + "type_CMCategory": "Kategorien", + "type_CMChannel": "Seiten", + "type_CMEvent": "Events", + "type_CMExternalChannel": "External Channels", + "type_CMExternalPage": "Externe Seiten", + "type_CMExternalProduct": "Produkte", + "type_CMImageMap": "Image Maps", + "type_CMLook": "Looks", + "type_CMPerson": "Editors", + "type_CMPicture": "Bilder", + "type_CMProduct": "Produkte", + "type_CMProductTeaser": "Produkt Teaser", + "type_CMRecipe": "Rezepte", + "type_CMVideo": "Videos", + "allTypes": "Alle Typen,", + "subject": "Schlagwort", + "type": "Typ", + "price": "Preis", + "brand": "Marke" + }, + "SortFilter": { + "sortBy": "Sortieren nach", + "Relevance": "Relevanz", + "Display Date Asc": "Anzeige Datum (aufsteigend)", + "Display Date Desc": "Anzeige Datum (absteigend)", + "Creation Date Asc": "Erstellungsdatum (aufsteigend)", + "Creation Date Desc": "Erstellungsdatum (absteigend)", + "Modification Date Asc": "Änderungsdatum (aufsteigend)", + "Modification Date Desc": "Änderungsdatum (absteigend)" + }, + "DetailPage": { + "relatedProducts": "Produkte", + "related": "Ähnliche Inhalte" + }, + "CheckoutPage": { + "successTitle": "Checkout abgeschlossen", + "successMessage": "

Danke für Ihre Bestellung.

Unser Team ist gerade dabei, Ihre Bestellung zu verpacken und zu versenden.

Wir werden Sie informieren, sobald die Ware auf dem Weg zu Ihnen ist.

", + "continueShopping": "Weiter einkaufen" + }, + "CMArticle": "Story", + "CMCategory": "Category", + "CMChannel": "Page", + "CMEvent": "Event", + "CMExternalChannel": "External Channel", + "CMExternalPage": "External Page", + "CMExternalProduct": "Product", + "CMImageMap": "Image Map", + "CMLook": "Look", + "CMPerson": "Editor", + "CMPicture": "Picture", + "CMProduct": "Product", + "CMProductTeaser": "Product Teaser", + "CMRecipe": "Recipe", + "CMVideo": "Video", + "commonSize": "Size", + "color": "Color", + "style": "Style", + "gender": "Gender", + "season": "Season", + "landscape_ratio16x9": "Ausschnitt 16:9", + "portrait_ratio2x3": "Ausschnitt 2:3", + "landscape_ratio4x3": "Ausschnitt 4:3", + "landscape_ratio8x3": "Ausschnitt 8:3", + "portrait_ratio1x1": "Ausschnitt 1:1", + "OfferPrice_USD": "Preis", + "({* TO 100])": "Unter 100€", + "({100 TO 200])": "Zwischen 100€ und 200€", + "({200 TO 300])": "Zwischen 200€ und 300€", + "({300 TO 400])": "Zwischen 300€ und 400€", + "({400 TO 500])": "Zwischen 400€ und 500€", + "({500 TO *})": "Über 500€", + "mobile-portrait": "(orientation: portrait) and (max-width: 767px)", + "tablet-portrait": "(orientation: portrait) and (min-width: 768px)" +} diff --git a/apps/spark/public/locales/en/translation.json b/apps/spark/public/locales/en/translation.json new file mode 100644 index 0000000..27360c5 --- /dev/null +++ b/apps/spark/public/locales/en/translation.json @@ -0,0 +1,137 @@ +{ + "Button": { + "defaultText": "Read more", + "addToCart" : "Add to cart", + "addMoreCart" : "Add more", + "downloadText": "Download" + }, + "CartList": { + "emptyText": "Your cart is currently empty.", + "product": "Product:", + "quantity": "Quantity:", + "price": "Price:", + "description": "Description:" + }, + "DetailedCart": { + "headline": "Cart", + "emptyText": "Your cart is currently empty.", + "description": "Description", + "price": "Price", + "quantity": "Quantity", + "total": "Total", + "checkoutButtonText": "Checkout", + "continueShopping": "Continue Shopping", + "cartTotals": "Cart Totals", + "subtotalInclTax": "Subtotal (incl. tax)", + "tax": "Tax" + }, + "HeaderCart": { + "title": "Cart" + }, + "HeaderSearchForm": { + "close": "Close search", + "label": "Search", + "placeholder": "Search ...", + "submit": "Search" + }, + "Loading": { + "loading": "loading ..." + }, + "SideCart": { + "title": "Cart", + "close": "Close", + "openCartButtonText": "Go to Cart" + }, + "Ticker": { + "title": "Breaking News" + }, + "FacetFilters": { + "title": "Filters" + }, + "ProductAssets": { + "downloads": "Downloads" + }, + "Search": { + "Header": { + "title": "Search Results", + "message": "Your search for {{query}} returned {{totalCount}} hits." + } + }, + "SearchFilter": { + "closeSearch": "Close search", + "filters": "Filters", + "type_CMArticle": "Stories", + "type_CMCategory": "Categories", + "type_CMChannel": "Pages", + "type_CMEvent": "Events", + "type_CMExternalChannel": "External Channels", + "type_CMExternalPage": "External Pages", + "type_CMExternalProduct": "Products", + "type_CMImageMap": "Image Maps", + "type_CMLook": "Looks", + "type_CMPerson": "Editors", + "type_CMPicture": "Pictures", + "type_CMProduct": "Products", + "type_CMProductTeaser": "Product Teasers", + "type_CMRecipe": "Recipes", + "type_CMVideo": "Videos", + "allTypes": "All types", + "subject": "Tag", + "type": "Type", + "price": "Price", + "brand": "Brand" + }, + "SortFilter": { + "sortBy": "Sort by", + "Relevance": "Relevance", + "Display Date Asc": "Display Date (ascending)", + "Display Date Desc": "Display Date (descending)", + "Creation Date Asc": "Creation Date (ascending)", + "Creation Date Desc": "Creation Date (descending)", + "Modification Date Asc": "Modification Date (ascending)", + "Modification Date Desc": "Modification Date (descending)" + }, + "DetailPage": { + "relatedProducts": "Products", + "related": "Related" + }, + "CheckoutPage": { + "successTitle": "Checkout Completed", + "successMessage": "

Thank you for your order.

Our team is currently packing and shipping your order.
We will inform you as soon as the goods are on their way to you.

", + "continueShopping": "Continue Shopping" + }, + "CMArticle": "Story", + "CMCategory": "Category", + "CMChannel": "Page", + "CMEvent": "Event", + "CMExternalChannel": "External Channel", + "CMExternalPage": "External Page", + "CMExternalProduct": "Product", + "CMImageMap": "Image Map", + "CMLook": "Look", + "CMPerson": "Editor", + "CMPicture": "Picture", + "CMProduct": "Product", + "CMProductTeaser": "Product Teaser", + "CMRecipe": "Recipe", + "CMVideo": "Video", + "commonSize": "Size", + "color": "Color", + "style": "Style", + "gender": "Gender", + "season": "Season", + "landscape_ratio16x9": "Crop 16:9", + "portrait_ratio2x3": "Crop 2:3", + "landscape_ratio4x3": "Crop 4:3", + "landscape_ratio8x3": "Crop 8:3", + "portrait_ratio1x1": "Crop 1:1", + "OfferPrice_USD": "Price", + "({* TO 100])": "Less than $100", + "({100 TO 200])": "Between $100 and $200", + "({200 TO 300])": "Between $200 and $300", + "({300 TO 400])": "Between $300 and $400", + "({400 TO 500])": "Between $400 and $500", + "({500 TO *})": "More than $500", + "mobile-portrait": "(orientation: portrait) and (max-width: 767px)", + "tablet-portrait": "(orientation: portrait) and (min-width: 768px)" +} diff --git a/apps/spark/public/locales/fr/translation.json b/apps/spark/public/locales/fr/translation.json new file mode 100644 index 0000000..efed29b --- /dev/null +++ b/apps/spark/public/locales/fr/translation.json @@ -0,0 +1,137 @@ +{ + "Button": { + "defaultText": "En savoir plus", + "addToCart" : "Ajouter au panier", + "addMoreCart" : "Ajouter plus", + "downloadText": "Télécharger" + }, + "CartList": { + "emptyText": "Votre Panier es vide.", + "product": "Produit:", + "quantity": "Quantité:", + "price": "Prix:", + "description": "Description:" + }, + "DetailedCart": { + "headline": "Panier", + "emptyText": "Votre Panier est vide.", + "description": "Description", + "price": "Prix", + "quantity": "Quantité", + "total": "Total", + "checkoutButtonText": "Aller à la caisse", + "continueShopping": "Continuer les achats", + "cartTotals": "Totaux du panier", + "subtotalInclTax": "Sous-total (TTC)", + "tax": "Taxe" + }, + "HeaderCart": { + "title": "Panier" + }, + "HeaderSearchForm": { + "close": "Fermer la recherche", + "label": "Recherche", + "placeholder": "Rechercher ...", + "submit": "Rechercher" + }, + "Loading": { + "loading": "chargement ..." + }, + "SideCart": { + "title": "Panier d'achat", + "close": "Fermer", + "openCartButtonText": "Aller au panier" + }, + "Ticker": { + "title": "Breaking News" + }, + "FacetFilters": { + "title": "Filtre" + }, + "ProductAssets": { + "downloads": "Téléchargements" + }, + "Search": { + "Header": { + "title": "Search Results", + "message": "Your search for {{query}} returned {{totalCount}} hits." + } + }, + "SearchFilter": { + "closeSearch": "Fermer la recherche", + "filters": "Filtres", + "type_CMArticle": "Stories", + "type_CMCategory": "Categories", + "type_CMChannel": "Pages", + "type_CMEvent": "Events", + "type_CMExternalChannel": "External Channels", + "type_CMExternalPage": "External Pages", + "type_CMExternalProduct": "Products", + "type_CMImageMap": "Image Maps", + "type_CMLook": "Looks", + "type_CMPerson": "Editors", + "type_CMPicture": "Pictures", + "type_CMProduct": "Products", + "type_CMProductTeaser": "Product Teasers", + "type_CMRecipe": "Recipes", + "type_CMVideo": "Videos", + "allTypes": "Tous les types", + "subject": "Étiquette", + "type": "Type", + "price": "prix", + "brand": "Marque" + }, + "SortFilter": { + "sortBy": "Trier par", + "Relevance": "Pertinence", + "Display Date Asc": "Date d'affichage (croissant)", + "Display Date Desc": "Date d'affichage (décroissant)", + "Creation Date Asc": "Date de création (croissante)", + "Creation Date Desc": "Date de création (décroissant)", + "Modification Date Asc": "Date de modification (croissante)", + "Modification Date Desc": "Date de modification (décroissant)" + }, + "DetailPage": { + "relatedProducts": "Produits", + "related": "Contenu connexe" + }, + "CheckoutPage": { + "successTitle": "Checkout terminé", + "successMessage": "

Merci pour votre commande.

Notre équipe est actuellement en train d'emballer et d'expédier votre commande.
Nous vous informerons dès que les marchandises seront en route vers vous.

", + "continueShopping": "Poursuivre les achats" + }, + "CMArticle": "Story", + "CMCategory": "Category", + "CMChannel": "Page", + "CMEvent": "Event", + "CMExternalChannel": "External Channel", + "CMExternalPage": "External Page", + "CMExternalProduct": "Product", + "CMImageMap": "Image Map", + "CMLook": "Look", + "CMPerson": "Editor", + "CMPicture": "Picture", + "CMProduct": "Product", + "CMProductTeaser": "Product Teaser", + "CMRecipe": "Recipe", + "CMVideo": "Video", + "commonSize": "Size", + "color": "Color", + "style": "Style", + "gender": "Gender", + "season": "Season", + "landscape_ratio16x9": "Crop 16:9", + "portrait_ratio2x3": "Crop 2:3", + "landscape_ratio4x3": "Crop 4:3", + "landscape_ratio8x3": "Crop 8:3", + "portrait_ratio1x1": "Crop 1:1", + "OfferPrice_USD": "Price", + "({* TO 100])": "Less than $100", + "({100 TO 200])": "Between $100 and $200", + "({200 TO 300])": "Between $200 and $300", + "({300 TO 400])": "Between $300 and $400", + "({400 TO 500])": "Between $400 and $500", + "({500 TO *})": "More than $500", + "mobile-portrait": "(orientation: portrait) and (max-width: 767px)", + "tablet-portrait": "(orientation: portrait) and (min-width: 768px)" +} diff --git a/apps/spark/public/locales/it/translation.json b/apps/spark/public/locales/it/translation.json new file mode 100644 index 0000000..b51c9c0 --- /dev/null +++ b/apps/spark/public/locales/it/translation.json @@ -0,0 +1,137 @@ +{ + "Button": { + "defaultText": "Scorpi di più", + "addToCart" : "Aggiungi al carrello", + "addMoreCart" : "Aggiungere altro", + "downloadText": "Scaricare" + }, + "CartList": { + "emptyText": "La shopping bag è vuota.", + "product": "Prodotto:", + "quantity": "Quantità:", + "price": "Prezzo:", + "description": "Descrizione:" + }, + "DetailedCart": { + "headline": "Shopping Bag", + "emptyText": "La shopping bag è vuota.", + "description": "Descrizione", + "price": "Prezzo", + "quantity": "Quantità", + "total": "Totale", + "checkoutButtonText": "Cassa", + "continueShopping": "Continua lo shopping", + "cartTotals": "Totali del carrello", + "subtotalInclTax": "Subtotale (tasse incluse)", + "tax": "Tassa" + }, + "HeaderCart": { + "title": "Carrello" + }, + "HeaderSearchForm": { + "close": "Chiudi la ricerca", + "label": "Ricerca", + "placeholder": "Cerca ...", + "submit": "Cerca" + }, + "Loading": { + "loading": "caricamento ..." + }, + "SideCart": { + "title": "Carrello", + "close": "Chiudere", + "openCartButtonText": "Vai al carrello" + }, + "Ticker": { + "title": "Ultime notizie" + }, + "FacetFilters": { + "title": "Filtro" + }, + "ProductAssets": { + "downloads": "Scaricamenti" + }, + "Search": { + "Header": { + "title": "Search Results", + "message": "Your search for {{query}} returned {{totalCount}} hits." + } + }, + "SearchFilter": { + "closeSearch": "Chiudere la ricerca", + "filters": "Filtri", + "type_CMArticle": "Stories", + "type_CMCategory": "Categories", + "type_CMChannel": "Pages", + "type_CMEvent": "Events", + "type_CMExternalChannel": "External Channels", + "type_CMExternalPage": "External Pages", + "type_CMExternalProduct": "Products", + "type_CMImageMap": "Image Maps", + "type_CMLook": "Looks", + "type_CMPerson": "Editors", + "type_CMPicture": "Pictures", + "type_CMProduct": "Products", + "type_CMProductTeaser": "Product Teasers", + "type_CMRecipe": "Recipes", + "type_CMVideo": "Videos", + "allTypes": "Tutti i tipi", + "subject": "Tag", + "type": "Tipo", + "price": "Prezzo", + "brand": "Segno" + }, + "SortFilter": { + "sortBy": "Ordina per", + "Relevance": "Rilevanza", + "Display Date Asc": "Data di visualizzazione (ascendente)", + "Display Date Desc": "Data di visualizzazione (decrescente)", + "Creation Date Asc": "Data di creazione (ascendente)", + "Creation Date Desc": "Data di creazione (decrescente)", + "Modification Date Asc": "Data di modifica (ascendente)", + "Modification Date Desc": "Data di modifica (decrescente)" + }, + "DetailPage": { + "relatedProducts": "Prodotti", + "related": "Contenuto relativo" + }, + "CheckoutPage": { + "successTitle": "Checkout completato", + "successMessage": "

Grazie per il suo ordine.

Il nostro team sta attualmente imballando e spedendo il suo ordine.
La informeremo non appena la merce sarà in viaggio verso di lei.

", + "continueShopping": "Continua lo shopping" + }, + "CMArticle": "Story", + "CMCategory": "Category", + "CMChannel": "Page", + "CMEvent": "Event", + "CMExternalChannel": "External Channel", + "CMExternalPage": "External Page", + "CMExternalProduct": "Product", + "CMImageMap": "Image Map", + "CMLook": "Look", + "CMPerson": "Editor", + "CMPicture": "Picture", + "CMProduct": "Product", + "CMProductTeaser": "Product Teaser", + "CMRecipe": "Recipe", + "CMVideo": "Video", + "commonSize": "Size", + "color": "Color", + "style": "Style", + "gender": "Gender", + "season": "Season", + "landscape_ratio16x9": "Crop 16:9", + "portrait_ratio2x3": "Crop 2:3", + "landscape_ratio4x3": "Crop 4:3", + "landscape_ratio8x3": "Crop 8:3", + "portrait_ratio1x1": "Crop 1:1", + "OfferPrice_USD": "Price", + "({* TO 100])": "Less than $100", + "({100 TO 200])": "Between $100 and $200", + "({200 TO 300])": "Between $200 and $300", + "({300 TO 400])": "Between $300 and $400", + "({400 TO 500])": "Between $400 and $500", + "({500 TO *})": "More than $500", + "mobile-portrait": "(orientation: portrait) and (max-width: 767px)", + "tablet-portrait": "(orientation: portrait) and (min-width: 768px)" +} diff --git a/apps/spark/public/logo192.png b/apps/spark/public/logo192.png deleted file mode 100644 index fc44b0a..0000000 Binary files a/apps/spark/public/logo192.png and /dev/null differ diff --git a/apps/spark/public/logo512.png b/apps/spark/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/apps/spark/public/logo512.png and /dev/null differ diff --git a/apps/spark/public/manifest.json b/apps/spark/public/manifest.json index 080d6c7..1f2f141 100644 --- a/apps/spark/public/manifest.json +++ b/apps/spark/public/manifest.json @@ -6,16 +6,6 @@ "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" } ], "start_url": ".", diff --git a/apps/spark/src/components/App/App.tsx b/apps/spark/src/components/App/App.tsx index 7b502f5..03cd18b 100644 --- a/apps/spark/src/components/App/App.tsx +++ b/apps/spark/src/components/App/App.tsx @@ -4,6 +4,7 @@ import { ApolloProvider } from "@apollo/client"; import { HelmetProvider } from "react-helmet-async"; import { ThemeProvider } from "styled-components"; import { SiteContextProvider } from "../../context/SiteContextProvider"; +import CartContextProvider from "../../context/CartContext"; import { getRootSegment, isAPQEnabled, setLogLevel } from "../../utils/App/App"; import PreviewPage from "../../pages/PreviewPage"; import { initializeApollo } from "../../utils/App/Apollo"; @@ -44,27 +45,32 @@ const App: FC = () => { - - - - - - - {isPreview() && } - {isPreview() && ( - - )} + + + + + + + + {isPreview() && } + {isPreview() && ( + + )} - - - - - - - + + + + + + + + diff --git a/apps/spark/src/components/App/AppRoutes.tsx b/apps/spark/src/components/App/AppRoutes.tsx index 6058cb1..330b63b 100644 --- a/apps/spark/src/components/App/AppRoutes.tsx +++ b/apps/spark/src/components/App/AppRoutes.tsx @@ -5,8 +5,10 @@ import Page from "../../pages/Page"; import ProductPage from "../../pages/ProductPage"; import CategoryPage from "../../pages/CategoryPage"; import SearchPage from "../../pages/SearchPage"; +import CartPage from "../../pages/CartPage"; import TopicPage from "../../pages/TopicPage"; import AuthorPage from "../../pages/AuthorPage"; +import CheckoutPage from "../Checkout/CheckoutPage"; /** * The site specific routes of the app @@ -15,6 +17,10 @@ import AuthorPage from "../../pages/AuthorPage"; const AppRoutes: FC = () => { return ( + {/* Cart */} + + {/* Checkout */} + {/* Search */} {/* Tag */} diff --git a/apps/spark/src/components/App/GlobalStyle.tsx b/apps/spark/src/components/App/GlobalStyle.tsx index 0dbcb93..052c0a3 100644 --- a/apps/spark/src/components/App/GlobalStyle.tsx +++ b/apps/spark/src/components/App/GlobalStyle.tsx @@ -17,22 +17,84 @@ export const GlobalStyle = createGlobalStyle` --font-size-text: 16px; --font-size-text-small: 14px; --line-height: 1.2rem; - /* COLORS */ - --color-background-light: #fff; - --color-background-dark: #1a1a1a; + + /* COLORS - primary */ + --color-red: #dd342b; + --color-magenta: #d82eb4; + --color-dark-grey: #363936; + + /* COLORS - secondary */ + --color-purple: #672779; + --color-blue: #006cae; + --color-turquoise: #6fc3b8; + --color-green: #2fac66; + --color-yellow: #efdf0f; + --color-light-grey: #f4f4f4; + --color-black: #000; + --color-white: #fff; + + --color-background-light: var(--color-white); + --color-background-dark: var(--color-dark-grey); --color-background-grey: #333; - --color-background-light-grey: #efefed; - --color-menu-shadow: hsla(0, 0%, 0%, 0.5); - --color-background-image: #ccc; - --color-green-highlight: #6fc3b8; - --color-green-highlight-hover: var(--color-background-dark); - --color-green-highlight-active: var(--color-background-grey); - --color-font-cta-hover: var(--color-background-light); + --color-background-light-grey: var(--color-light-grey); + --color-background-image: var(--color-light-grey); + + --color-green-highlight: #bbff00; + --color-green-highlight-hover: #ddff00; + --color-green-highlight-active: var(--color-green-highlight-hover); + + /* GRADIENTS - primary */ + --gradient-red-to-magenta: linear-gradient(135deg, var(--color-red), var(--color-magenta)); + --gradient-purple-to-blue: linear-gradient(135deg, var(--color-purple), var(--color-blue)); + --gradient-magenta-to-purple: linear-gradient(135deg, var(--color-magenta), var(--color-purple)); + + /* GRADIENTS - secondary */ + --gradient-turquoise-to-purple: linear-gradient(135deg, var(--color-turquoise), var(--color-purple)); + --gradient-blue-to-green: linear-gradient(135deg, var(--color-blue), var(--color-green)); + --gradient-yellow-to-red: linear-gradient(135deg, var(--color-yellow), var(--color-red)); + --gradient-blue-to-turquoise: linear-gradient(135deg, var(--color-blue), var(--color-turquoise)); + --gradient-dark-to-light-grey: linear-gradient(135deg, var(--color-dark-grey), var(--color-light-grey)); + --gradient-light-to-dark-grey: linear-gradient(135deg, var(--color-light-grey), var(--color-dark-grey)); + /* SIZES */ --padding-small: 12px; --padding-medium: 24px; --padding-large: 48px; --screen-size-max: 1140px; + + /* BORDERS */ + --border-radius-small: 3px; + --border-radius-medium: 6px; + --border-radius-large: 9px; + --border-width-small: 1px; + --border-width-medium: 2px; + --border-width-large: 5px; + + /* CTA buttons */ + --cta-background: var(--color-white); + --cta-text-color: var(--color-black); + --cta-border: var(--border-width-small) solid var(--cta-text-color); + --cta-border-radius: var(--border-radius-small); + --cta-background-hover: var(--color-light-grey); + --cta-text-color-hover: var(--cta-text-color); + --cta-border-hover: var(--border-width-small) solid var(--color-light-grey); + --cta-background-active: var(--color-light-grey); + --cta-text-color-active: var(--cta-text-color); + --cta-border-active: var(--border-width-small) solid var(--color-light-grey); + + --cta-primary-background: var(--color-black); + --cta-primary-text-color: var(--color-white); + --cta-primary-border: var(--border-width-small) solid var(--color-black); + --cta-primary-border-radius: var(--border-radius-small); + --cta-primary-background-hover: var(--color-light-grey); + --cta-primary-text-color-hover: var(--color-black); + --cta-primary-border-hover: var(--border-width-small) solid var(--color-light-grey); + --cta-primary-background-active: var(--color-light-grey); + --cta-primary-text-color-active: var(--cta-primary-text-color); + --cta-primary-border-active: var(--border-width-small) solid var(--color-light-grey); + + /* Shadows */ + --drop-shadow: 0px 0px 20px rgba(0,0,0,0.25); } @font-face { diff --git a/apps/spark/src/components/Button/Button.tsx b/apps/spark/src/components/Button/Button.tsx index 57217aa..a3960f4 100644 --- a/apps/spark/src/components/Button/Button.tsx +++ b/apps/spark/src/components/Button/Button.tsx @@ -1,40 +1,51 @@ import React from "react"; +import { useTranslation } from "react-i18next"; import styled from "styled-components"; import Link from "../Link/Link"; interface Props { - linkTarget: any; + linkTarget?: any; + clickHandler?: () => void; text?: string; additionalClass?: string; openInNewTab?: boolean; externalLink?: boolean; + primary?: boolean; } -export const StyledButton = styled(Link)` - display: inline-block; +export const StyledButton = styled.div<{ primary?: boolean }>` + background: ${(props) => (props.primary ? "var(--cta-primary-background)" : "var(--cta-background)")}; + border-radius: ${(props) => (props.primary ? "var(--cta-primary-border-radius)" : "var(--cta-border-radius)")}; + border: ${(props) => (props.primary ? "var(--cta-primary-border)" : "var(--cta-border)")}; + color: ${(props) => (props.primary ? "var(--cta-primary-text-color)" : "var(--cta-text-color)")}; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-family: var(--font-family-headline); + font-size: var(--font-size-heading-3); + padding: 10px 20px 5px; + pointer-events: auto; position: relative; text-decoration: none; - color: #000; - background-color: var(--color-green-highlight); - border-radius: 0; - pointer-events: auto; text-transform: uppercase; - padding: 10px 20px 5px; - transition: all 0.1s ease; - font-size: var(--font-size-heading-3); - font-family: var(--font-family-headline); + transition: all 0.5s ease; + &:hover { - background-color: var(--color-green-highlight-hover); - color: var(--color-font-cta-hover); + background: ${(props) => (props.primary ? "var(--cta-primary-background-hover)" : "var(--cta-background-hover)")}; + border: ${(props) => (props.primary ? "var(--cta-primary-border-hover)" : "var(--cta-border-hover)")}; + color: ${(props) => (props.primary ? "var(--cta-primary-text-color-hover)" : "var(--cta-text-color-hover)")}; } &:active, &:focus { outline: none; - background-color: var(--color-green-highlight-active); + background: ${(props) => (props.primary ? "var(--cta-primary-background-active)" : "var(--cta-background-active)")}; + border: ${(props) => (props.primary ? "var(--cta-primary-border-active)" : "var(--cta-border-active)")}; + color: ${(props) => (props.primary ? "var(--cta-primary-text-color-active)" : "var(--cta-text-color-active)")}; box-shadow: - inset 0 0 0 1px #fff, - inset 0 0 0 2px #000; + inset 0 0 0 1px var(--cta-background-active), + inset 0 0 0 2px var(--cta-text-color-active); } & + & { @@ -42,36 +53,32 @@ export const StyledButton = styled(Link)` } `; -export const CMButton = styled.div` - font-family: var(--font-family-text); - font-size: var(--font-size-text-small); - line-height: 1.428571429; - cursor: pointer; - text-decoration: none; - background-color: hsla(0, 0%, 100%, 0.75); - border: none; - border-radius: 0; - &:hover { - background-color: #fff; - } - &:active, - &:focus { - background-color: #fff; - box-shadow: - inset 0 0 0 1px hsla(0, 0%, 100%, 0.75), - inset 0 0 0 2px #000; - } -`; +const Button: React.FC = ({ + linkTarget, + clickHandler, + text, + additionalClass, + openInNewTab = false, + externalLink = false, + primary = true, +}) => { + const { t } = useTranslation(); -const Button: React.FC = ({ linkTarget, text, additionalClass, openInNewTab = false, externalLink = false }) => { - text = text ? text : "Read more"; //todo add i18n + if (!text) { + // do not show default text "read more" for media / download links + text = + linkTarget && linkTarget.indexOf("/caas/v1/media") === 0 ? t("Button.downloadText") : t("Button.defaultText"); + } return ( {text} diff --git a/apps/spark/src/components/CTA/CTA.tsx b/apps/spark/src/components/CTA/CTA.tsx index 4467bb2..936ab8b 100644 --- a/apps/spark/src/components/CTA/CTA.tsx +++ b/apps/spark/src/components/CTA/CTA.tsx @@ -8,6 +8,7 @@ interface Props { targets?: Array; additionalClass?: string; additionalButtonClass?: string; + primary?: boolean; } export const StyledCTA = styled.div` @@ -17,7 +18,7 @@ export const StyledCTA = styled.div` text-shadow: none; `; -const CTA: React.FC = ({ targets, additionalClass, additionalButtonClass }) => { +const CTA: React.FC = ({ targets, additionalClass, additionalButtonClass, primary = true }) => { return ( {targets && @@ -32,6 +33,7 @@ const CTA: React.FC = ({ targets, additionalClass, additionalButtonClass additionalClass={additionalButtonClass} openInNewTab={target.openInNewTab} externalLink={target.externalLink} + primary={primary} /> ) ); diff --git a/apps/spark/src/components/Cart/CartList.tsx b/apps/spark/src/components/Cart/CartList.tsx new file mode 100644 index 0000000..078a29c --- /dev/null +++ b/apps/spark/src/components/Cart/CartList.tsx @@ -0,0 +1,136 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import styled, { css } from "styled-components"; +import { useCartContextState } from "../../context/CartContext"; +import ProductPricing from "../Product/ProductPricing"; +import { metaDataProperty } from "../../utils/Preview/MetaData"; +import Link from "../Link/Link"; +import Image from "../Media/Image"; + +const Cart = styled.div``; +const EmptyCart = styled.div` + margin-bottom: 8px; + width: 200px; + padding: 12px 0; + border-bottom: 1px solid #eeeeee; +`; +const CartItem = styled.div` + padding: 10px 0; + border-bottom: 1px solid #eeeeee; + margin-bottom: 10px; + display: flex; +`; +const ItemPicture = styled.div` + flex: 0 0 60px; +`; +const ItemProperties = styled.div` + padding-left: 10px; + flex: 2; +`; +const ItemRemove = styled.div` + flex: 0; + cursor: pointer; +`; +const Property = styled.div<{ hideName: boolean }>` + > div { + display: inline-block; + font-size: var(--font-size-text-small); + + > a { + color: #000; + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + + ${(props) => + props.hideName && + css` + > div:first-child { + border: 0; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + } + `} +`; +const TitleProperty = styled(Property)` + font-size: var(--font-size-text-small); + margin-bottom: 6px; +`; +const DescriptionProperty = styled(Property)` + display: none; +`; +const QuantityProperty = styled(Property)``; +const PriceProperty = styled(Property)` + display: inline-block; + float: right; + padding-right: 20px; +`; +const CartList: React.FC = () => { + const { cartItems, itemCount, removeProduct } = useCartContextState(); + const { t } = useTranslation(); + return ( + + {itemCount === 0 && {t("CartList.emptyText")}} + {itemCount > 0 && ( +
+ {cartItems.map((item, index) => { + return ( + + {item.product.picture && ( + + + + )} + + +
{t("CartList.product")}
+
+ {item.product.title} +
+
+ +
{t("CartList.quantity")}
+
{item.quantity}
+
+ +
{t("CartList.price")}
+
+ +
+
+ +
{t("CartList.description")}
+
{item.product.text}
+
+
+ + {item.quantity > 0 && ( + + )} + +
+ ); + })} +
+ )} +
+ ); +}; + +export default CartList; diff --git a/apps/spark/src/components/Cart/DetailedCart.tsx b/apps/spark/src/components/Cart/DetailedCart.tsx new file mode 100644 index 0000000..1a4d8a6 --- /dev/null +++ b/apps/spark/src/components/Cart/DetailedCart.tsx @@ -0,0 +1,279 @@ +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; +import styled, { css } from "styled-components"; +import { useCartContextState } from "../../context/CartContext"; +import Link from "../Link/Link"; +import ProductPricing, { formatPrice } from "../Product/ProductPricing"; +import { metaDataProperty } from "../../utils/Preview/MetaData"; +import Image, { StyledImage } from "../Media/Image"; +import Button, { StyledButton } from "../Button/Button"; +import { Headline } from "../Details/Detail"; +import { StyledCol } from "../PageGrid/Col"; +import QuantityButton, { CartQuantity } from "../Product/QuantityButton"; +import { useSiteContextState } from "../../context/SiteContextProvider"; +import BasketIcon from "./assets/basket.svg"; + +const Cart = styled.div` + display: flex; + + > div { + } +`; + +const EmptyCart = styled.div` + text-align: center; +`; + +const CartProducts = styled.div` + flex: 2; + padding-right: 12px; + + table { + border-collapse: collapse; + + thead { + height: 3em; + th { + &:nth-child(3) { + text-align: start; + } + } + } + + tbody > tr { + border-bottom: 1px solid var(--color-background-light-grey); + + &:last-child { + border: none; + } + + th { + text-align: center; + } + + td { + font-size: 14px; + padding: 0.5em 0.5em 1em 0; + + @media only screen and (max-width: 767px) { + padding: 30px 12px 30px 12px; + } + + // remove btn + :first-child { + ${StyledButton} { + padding: 0 8px; + aspect-ratio: 1/1; + border-color: transparent; + } + } + + // product thumbnail + :nth-child(2) { + width: 150px; + aspect-ratio: 1/1; + + ${StyledImage} { + border-radius: var(--border-radius-small); + } + } + + // product name + :nth-child(3) { + width: 340px; + font-family: var(--font-family-headline); + font-size: var(--font-size-heading-3); + + a { + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } + + // price + :nth-child(4) { + width: 100px; + text-align: end; + } + + // quantity + :nth-child(5) { + width: 150px; + ${CartQuantity} { + min-width: 130px; + } + } + + // total + :last-child { + width: 100px; + text-align: end; + + span { + color: #262626; + font-size: 16px; + } + } + } + } + } +`; +const CartCheckout = styled.div` + flex: 1; + background: #f9f9f9; + padding: 30px; + + h2 { + margin-top: 0; + } + + > div { + & > * { + flex: 1; + } + } +`; + +const CartContent = styled.div<{ addBorder?: boolean }>` + display: flex; + ${(props) => + props.addBorder && props.addBorder === true + ? css` + border-top: 1px solid #dbdbdb; + padding: 20px 0 0 0; + margin: 20px 0 37px 0; + ` + : css` + margin: 24px 0 6px; + `} +`; + +const DetailedCart: React.FC = () => { + const { rootSegment } = useSiteContextState(); + const { cartItems, itemCount, increase, decrease, removeProduct, total, hideSideCart } = useCartContextState(); + const { t } = useTranslation(); + + useEffect(() => { + hideSideCart(); + }, []); + + return ( + + {t("DetailedCart.headline")} + {itemCount === 0 && ( + + +

{t("DetailedCart.emptyText")}

+ + + + + {isInCart(banner) && ( + + )} + {!isInCart(banner) && ( + + )} )} diff --git a/apps/spark/src/components/Product/assets/wish-outline.svg b/apps/spark/src/components/Product/assets/wish-outline.svg new file mode 100644 index 0000000..85c0a55 --- /dev/null +++ b/apps/spark/src/components/Product/assets/wish-outline.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/spark/src/components/Product/assets/wish-solid.svg b/apps/spark/src/components/Product/assets/wish-solid.svg new file mode 100644 index 0000000..7831c26 --- /dev/null +++ b/apps/spark/src/components/Product/assets/wish-solid.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/spark/src/components/Product/assets/wish.svg b/apps/spark/src/components/Product/assets/wish.svg deleted file mode 100644 index 801c38e..0000000 --- a/apps/spark/src/components/Product/assets/wish.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/spark/src/components/Search/Filters/SearchFilter.tsx b/apps/spark/src/components/Search/Filters/SearchFilter.tsx index aa1e041..8c771ba 100644 --- a/apps/spark/src/components/Search/Filters/SearchFilter.tsx +++ b/apps/spark/src/components/Search/Filters/SearchFilter.tsx @@ -1,5 +1,6 @@ import React from "react"; import styled, { css } from "styled-components"; +import { useTranslation } from "react-i18next"; import { SearchFacet, useSearchStateContextState } from "../../../context/SearchStateContext"; import Chevron from "../assets/chevron.svg"; import ArrowBack from "../assets/arrow-back.svg"; @@ -117,11 +118,12 @@ const SearchFilter: React.FC = ({ title, entries, filterType = "checkbox" const { removeFacets, selectedFacets } = useSearchStateContextState(); const [isOpen, setIsOpen] = React.useState(true); + const { t } = useTranslation(); return ( setIsOpen(!isOpen)}> - {title} + {t(title)}
@@ -134,7 +136,7 @@ const SearchFilter: React.FC = ({ title, entries, filterType = "checkbox" }} > - All types + {t("SearchFilter.allTypes")} )} {entries?.map((item, index) => { @@ -144,7 +146,7 @@ const SearchFilter: React.FC = ({ title, entries, filterType = "checkbox" diff --git a/apps/spark/src/components/Search/Filters/StringFilterEntry.tsx b/apps/spark/src/components/Search/Filters/StringFilterEntry.tsx index e432d4d..1add1fb 100644 --- a/apps/spark/src/components/Search/Filters/StringFilterEntry.tsx +++ b/apps/spark/src/components/Search/Filters/StringFilterEntry.tsx @@ -1,5 +1,6 @@ import React from "react"; import { useSearchStateContextState } from "../../../context/SearchStateContext"; +import { getLocalizedLabel } from "../../../utils/Translation/TranslationHelper"; import { isActiveGroup, isFacetValueSelected, @@ -31,7 +32,8 @@ const StringFilterEntry: React.FC = ({ title, label, query, count }) => { > {!isSelected && ( - {label} + {/*TODO: Get localized tag label*/} + {title === "subject" ? label : getLocalizedLabel(title + "_" + label, "SearchFilter")} {count && count > 0 && {count}} )} diff --git a/apps/spark/src/components/Search/HeaderSearchForm.tsx b/apps/spark/src/components/Search/HeaderSearchForm.tsx index ce48174..354ae0d 100644 --- a/apps/spark/src/components/Search/HeaderSearchForm.tsx +++ b/apps/spark/src/components/Search/HeaderSearchForm.tsx @@ -1,11 +1,11 @@ import React, { FC, useEffect, useState } from "react"; import { useHistory, useLocation } from "react-router-dom"; +import { useTranslation } from "react-i18next"; import styled from "styled-components"; import { useSuggestLazyQuery } from "@coremedia-labs/graphql-layer"; import { useSearchStateContextState } from "../../context/SearchStateContext"; import Hamburger from "../Header/Hamburger"; import { useSiteContextState } from "../../context/SiteContextProvider"; -import { ApolloClientAlert } from "../Error/Alert"; import { LanguageChooserMenu } from "../Header/LanguageChooser"; import { SearchButton, SearchFormFieldSet, SearchFormLabel, SearchInput } from "./SearchQuery"; @@ -110,7 +110,9 @@ const StyledSearch = styled.div` `; const HeaderInput = styled(SearchInput)` - background-color: #fff; + background-color: var(--color-background-light-grey); + border: none; + box-shadow: none; @media screen and (max-width: 767px), screen and (min-width: 768px) and (max-width: 1199px) and (orientation: portrait) { @@ -194,10 +196,11 @@ const HeaderSearchForm: FC = () => { const { rootSegment } = useSiteContextState(); const location = useLocation(); const { setQuery } = useSearchStateContextState(); + const { t } = useTranslation(); const [suggestions, setSuggestions] = useState>([]); - const [getSuggestionQuery, { error }] = useSuggestLazyQuery(); + const [getSuggestionQuery, {}] = useSuggestLazyQuery(); const { siteId } = useSiteContextState(); const onChangeHandler = (text: string) => { @@ -242,8 +245,6 @@ const HeaderSearchForm: FC = () => { setSearchQuery(urlSearchParams.get("query") || ""); }, [location.search]); - if (error) return ; - return ( { /> - Search + {t("HeaderSearchForm.label")} onChangeHandler(event.target.value)} type="search" name="query" - placeholder="Search..." + placeholder={t("HeaderSearchForm.placeholder")} required={true} minLength={3} //onBlur={() => setSuggestions([])} value={searchQuery} /> - - Search + + {t("HeaderSearchForm.submit")} 0}> {suggestions && diff --git a/apps/spark/src/components/Search/Search.tsx b/apps/spark/src/components/Search/Search.tsx index ac601fe..41f47a3 100644 --- a/apps/spark/src/components/Search/Search.tsx +++ b/apps/spark/src/components/Search/Search.tsx @@ -1,5 +1,6 @@ import React from "react"; import styled from "styled-components"; +import { useTranslation } from "react-i18next"; import { useSearchPageContextState } from "../../context/SearchPageContext"; import { useSearchStateContextState } from "../../context/SearchStateContext"; import { StyledCol } from "../PageGrid/Col"; @@ -49,16 +50,16 @@ const SearchHeader = styled.div` const Search: React.FC = () => { const { totalCount } = useSearchPageContextState(); const { query } = useSearchStateContextState(); - + const { t } = useTranslation(); return ( -

Search Results

+

{t("Search.Header.title")}

{query && ( -
- Your search for {query} returned {totalCount} hits. -
+
)}
diff --git a/apps/spark/src/components/Search/SearchFilters.tsx b/apps/spark/src/components/Search/SearchFilters.tsx index f23ddc2..02f5bf2 100644 --- a/apps/spark/src/components/Search/SearchFilters.tsx +++ b/apps/spark/src/components/Search/SearchFilters.tsx @@ -1,5 +1,6 @@ import React, { useState } from "react"; import styled, { css } from "styled-components"; +import { useTranslation } from "react-i18next"; import SortFilter from "./SortFilter"; import FacetFilters from "./Filters/FacetFilters"; import FilterIcon from "./assets/filter.svg"; @@ -87,7 +88,7 @@ const SearchFilterPopupHead = styled.div<{ active: boolean }>` const SearchFilterButton = styled.button` position: relative; text-decoration: none; - color: var(--color-font-cta-hover); + color: var(--color-cta-font-hover); background-color: var(--color-green-highlight); border-radius: 0; pointer-events: auto; @@ -100,7 +101,7 @@ const SearchFilterButton = styled.button` &:hover { background-color: var(--color-green-highlight-hover); - color: var(--color-font-cta-hover); + color: var(--color-cta-font-hover); } &:active, @@ -118,6 +119,7 @@ const SearchFilterButton = styled.button` const SearchFilters: React.FC = () => { const [toggled, setToggled] = useState(false); + const { t } = useTranslation(); return ( <> @@ -128,13 +130,13 @@ const SearchFilters: React.FC = () => { }} > - Filters + {t("SearchFilters.filters")} -

Filters

+

{t("SearchFilters.filters")}

diff --git a/apps/spark/src/components/Search/SearchResult.tsx b/apps/spark/src/components/Search/SearchResult.tsx index 162e3c5..b669b7d 100644 --- a/apps/spark/src/components/Search/SearchResult.tsx +++ b/apps/spark/src/components/Search/SearchResult.tsx @@ -7,34 +7,37 @@ import { notEmpty } from "../../utils/Helpers"; import SearchBanner from "./SearchBanner"; export const MoreButton = styled.button` + background: var(--cta-background); + border-radius: var(--cta-border-radius); + border: var(--cta-border); + color: var(--cta-text-color); + cursor: pointer; display: block; + font-family: var(--font-family-headline); + font-size: var(--font-size-heading-3); margin: 0 auto; - border: none; - cursor: pointer; + padding: 10px 20px 5px; + pointer-events: auto; position: relative; text-decoration: none; - color: var(--color-font-cta-hover); - background-color: var(--color-green-highlight); - border-radius: 0; - pointer-events: auto; text-transform: uppercase; - padding: 10px 20px 5px; transition: all 0.1s ease; - font-size: var(--font-size-heading-3); - font-family: var(--font-family-headline); &:hover { - background-color: var(--color-green-highlight-hover); - color: var(--color-font-cta-hover); + background-color: var(--cta-background-hover); + border: var(--cta-border-hover); + color: var(--cta-text-color-hover); } &:active, &:focus { outline: none; - background-color: var(--color-green-highlight-active); + background: var(--cta-background-active); + border: var(--cta-border-active); + color: var(--cta-primary-text-color-active) " : " var(--cta-text-color-active); box-shadow: - inset 0 0 0 1px #fff, - inset 0 0 0 2px #000; + inset 0 0 0 1px var(--cta-background-active), + inset 0 0 0 2px var(--cta-text-color-active); } `; diff --git a/apps/spark/src/components/Search/SortFilter.tsx b/apps/spark/src/components/Search/SortFilter.tsx index d2e65f5..3fa7b5b 100644 --- a/apps/spark/src/components/Search/SortFilter.tsx +++ b/apps/spark/src/components/Search/SortFilter.tsx @@ -2,7 +2,9 @@ import React, { ChangeEvent, useMemo } from "react"; import { SortFieldWithOrder } from "@coremedia-labs/graphql-layer"; import { useHistory, useLocation } from "react-router-dom"; import styled from "styled-components"; +import { useTranslation } from "react-i18next"; import { useSearchStateContextState } from "../../context/SearchStateContext"; +import { getLocalizedLabel } from "../../utils/Translation/TranslationHelper"; const StyledSort = styled.div` height: 74px; @@ -35,6 +37,7 @@ const SortFilter: React.FC = () => { const urlSearchParams = useMemo(() => new URLSearchParams(location.search), [location.search]); const { setSortField, sortField, sortFields } = useSearchStateContextState(); + const { t } = useTranslation(); const onSortChange = (e: ChangeEvent): void => { e.preventDefault(); @@ -53,12 +56,12 @@ const SortFilter: React.FC = () => { <> {sortFields && ( - +