Compare commits

...

2 Commits

Author SHA1 Message Date
85b18161fd kolorki 2026-01-28 15:03:41 +01:00
09c14785e7 opinie, dodaj do koszyka, labelki 2026-01-27 10:39:21 +01:00
17 changed files with 761 additions and 309 deletions

View File

@@ -34,7 +34,7 @@ function idmObserveEachOncePM(elements, callback, options = {}) {
{
threshold: 0.1,
...options,
}
},
);
elements.forEach((el) => observer.observe(el));
@@ -45,7 +45,7 @@ idmObserveEachOncePM(
document.querySelectorAll(".idm_picture__module"),
(entry) => {
idmPictureModuleProductsPM(entry.target);
}
},
);
// GRAPHQL QUERY
@@ -144,6 +144,7 @@ function idmGetSingleProdGraphQLPM({
name
link
type
icon
${labels ? "zones" : ""}
${
opinions
@@ -239,7 +240,7 @@ async function idmHandleAddToBasketPM(e) {
Alertek.Error(
idmPhotoModuleLiteralsPM[
"Coś poszło nie tak podczas dodawania do koszyka. Spróbuj ponownie lub odśwież stronę"
]
],
);
buttonEl.innerHTML = `<span>${buttonEl.dataset.error}</span>`;
buttonEl.classList.add("--error");
@@ -267,20 +268,10 @@ function idmMarkupAddToBasketPM(prodData) {
return `<form class="add_to_basket" action="/basketchange.php" type="post">
<input class="product__add_mode" type="hidden" value="1" name="mode">
<input class="product__add_id" type="hidden" value="${
prodData.id
}" name="mode">
<input class="product__add_size" type="hidden" value="${
prodData.sizes?.[0]?.id || "uniw"
}" name="mode">
<input class="product__add_number" type="hidden" value="${
prodData.unit?.sellBy || 1
}" name="mode">
<button class="btn --solid --medium add_to_basket__button" tabindex="0" data-success="${
idmPhotoModuleLiteralsPM["Dodany"]
}" data-error="${idmPhotoModuleLiteralsPM["Wystąpił błąd"]}" data-text="${
idmPhotoModuleLiteralsPM["Do koszyka"]
}">
<input class="product__add_id" type="hidden" value="${prodData.id}" name="mode">
<input class="product__add_size" type="hidden" value="${prodData.sizes?.[0]?.id || "uniw"}" name="mode">
<input class="product__add_number" type="hidden" value="${prodData.unit?.sellBy || 1}" name="mode">
<button class="btn --solid --medium add_to_basket__button" tabindex="0" data-success="${idmPhotoModuleLiteralsPM["Dodany"]}" data-error="${idmPhotoModuleLiteralsPM["Wystąpił błąd"]}" data-text="${idmPhotoModuleLiteralsPM["Do koszyka"]}">
<span>${idmPhotoModuleLiteralsPM["Do koszyka"]}</span>
</button>
</form>`;
@@ -291,9 +282,7 @@ function idmMarkupPricePM({ prodData, addToBasket }) {
let priceMarkup;
if (!addToBasket)
priceMarkup = `<div class="product_prices"><span class="price --normal --main">${
prodData.price?.price?.[app_shop.vars.priceType]?.formatted
}</span></div>`;
priceMarkup = `<div class="product_prices"><span class="price --normal --main">${prodData.price?.price?.[app_shop.vars.priceType]?.formatted}</span></div>`;
else {
const currentSize = prodData?.sizes?.[0] || prodData;
@@ -313,9 +302,7 @@ function idmMarkupPricePM({ prodData, addToBasket }) {
const maxPercent = currentSize?.price?.youSavePercent;
priceMarkup = `
<div class="product_prices ${omnibusPrice ? `--omnibus` : ""} ${
isOmnibusHigher ? `--omnibus-higher` : ""
} ${omnibusPrice === maxPrice ? "--omnibus-short" : ""}">
<div class="product_prices ${omnibusPrice ? `--omnibus` : ""} ${isOmnibusHigher ? `--omnibus-higher` : ""} ${omnibusPrice === maxPrice ? "--omnibus-short" : ""}">
<span class="price --normal --main">${price}</span>
${
omnibusPrice && typeof omnibusPercent === "number"
@@ -382,7 +369,7 @@ async function idmPictureModuleProductsPM(containerEL) {
opinions: isOpinions,
addToBasket: isAddToBasket,
})}`,
""
"",
)}}`,
}),
});
@@ -420,41 +407,32 @@ async function idmPictureModuleProductsPM(containerEL) {
// OPINIE
let opinionsHTML = "";
if (isOpinions) {
if (
isOpinions &&
typeof prodData.opinion?.rating === "number" &&
typeof prodData.opinion?.count === "number"
) {
opinionsHTML = `
<div class="product_opinions__stars">
<i class="icon-star ${
prodData.opinion?.rating > 0.5 ? "--active" : ""
}"></i>
<i class="icon-star ${
prodData.opinion?.rating > 1.5 ? "--active" : ""
}"></i>
<i class="icon-star ${
prodData.opinion?.rating > 2.5 ? "--active" : ""
}"></i>
<i class="icon-star ${
prodData.opinion?.rating > 3.5 ? "--active" : ""
}"></i>
<i class="icon-star ${
prodData.opinion?.rating > 4.5 ? "--active" : ""
}"></i>
<i class="icon-star ${prodData.opinion.rating > 0.5 ? "--active" : ""}"></i>
<i class="icon-star ${prodData.opinion.rating > 1.5 ? "--active" : ""}"></i>
<i class="icon-star ${prodData.opinion.rating > 2.5 ? "--active" : ""}"></i>
<i class="icon-star ${prodData.opinion.rating > 3.5 ? "--active" : ""}"></i>
<i class="icon-star ${prodData.opinion.rating > 4.5 ? "--active" : ""}"></i>
</div>
<span class="product_opinions__score">${
prodData.opinion.rating
} / 5.00 </span>
<span class="product_opinions__score">${prodData.opinion.rating} / 5.00 </span>
<span class="product_opinions__count">${prodData.opinion.count}</span>
`;
}
prodEl.innerHTML = `
${isLabels ? `<strong class="label_icons">${labelsHTML}</strong>` : ""}
${
isOpinions
? `<div class="product_opinions">${opinionsHTML}</div>`
: ""
}
<a class="product_name" href="${prodData.link}">${prodData.name}</a>
${idmMarkupPricePM({ prodData, addToBasket: isAddToBasket })}
${isAddToBasket ? idmMarkupAddToBasketPM(prodData) : ""}
<a href="${prodData.link}" class="product_icon --mobile"><img src="${prodData.icon}" alt="${prodData.name}" tabindex="-1"/></a>
<div class="product_info__container">
${isLabels ? `<strong class="label_icons">${labelsHTML}</strong>` : ""}
${isOpinions ? `<div class="product_opinions">${opinionsHTML}</div>` : ""}
<a class="product_name" href="${prodData.link}">${prodData.name}</a>
${idmMarkupPricePM({ prodData, addToBasket: isAddToBasket })}
${isAddToBasket ? idmMarkupAddToBasketPM(prodData) : ""}
</div>
`;
idmInitEventsPM({
@@ -464,7 +442,7 @@ async function idmPictureModuleProductsPM(containerEL) {
});
} catch (err) {
allProdEl?.forEach((prodEl) =>
prodEl.closest(".idm_picture__product")?.remove()
prodEl.closest(".idm_picture__product")?.remove(),
);
console.error(err);
}
@@ -474,21 +452,31 @@ async function idmPictureModuleProductsPM(containerEL) {
document.body.addEventListener("click", (e) => {
if (app_shop.vars.view === 3 || app_shop.vars.view === 4) return;
const prodContainerEl = e.target.closest(".idm_picture__product");
const moduleCotnainer = e.target.closest(".idm_picture__module");
const prodContainerEl = e.target
.closest(".idm_picture__product")
?.querySelector(".product_info");
if (!prodContainerEl || !moduleCotnainer) return;
// ClearShow
if (!prodContainerEl)
return document
.querySelector(".idm_picture__product.--show")
?.classList?.remove("--show");
if (prodContainerEl.classList.contains("--show"))
return prodContainerEl.classList.remove("--show");
// if(!prodContainerEl) return document.querySelector(".idm_picture__product.--show")?.classList?.remove("--show");
// if(prodContainerEl.classList.contains("--show")) return prodContainerEl.classList.remove("--show");
const moduleContainer = prodContainerEl.closest(".idm_picture__module");
if (!moduleContainer) return;
// const moduleContainer = prodContainerEl.closest(".idm_picture__module");
// if(!moduleContainer) return;
moduleContainer
.querySelectorAll(".idm_picture__product.--show")
.forEach((prodConEl) => prodConEl.classList.remove("--show"));
prodContainerEl.classList.add("--show");
// moduleContainer.querySelectorAll(".idm_picture__product.--show").forEach(prodConEl=>prodConEl.classList.remove("--show"));
// prodContainerEl.classList.add("--show");
const computedStyles = window.getComputedStyle(moduleCotnainer);
const backgroundColor = computedStyles.getPropertyValue("--photo-mod-col-bg");
const textColor = computedStyles.getPropertyValue("--photo-mod-col-text");
new Modal({
element: prodContainerEl,
classList: "--mobile --photo-prod-mobile",
afterShow: (modal) => {
modal.style.setProperty("--photo-prod-box-bg", backgroundColor || "#fff");
modal.style.setProperty("--photo-prod-box-text", textColor || "#111");
},
});
});

View File

@@ -1,7 +1,8 @@
<style>
.idm_picture__module {
--photo-prod-box-bg: #fff;
--photo-prod-box-text: #111;
--photo-prod-box-bg: var(--photo-mod-col-bg, #fff);
--photo-prod-box-text: var(--photo-mod-col-text, #111);
--photo-prod-point-shadow: rgba(255, 255, 255, 0.5);
--photo-prod-box-pad-top: 1rem;
@@ -32,8 +33,7 @@
position: absolute;
z-index: 10;
}
.idm_picture__product:hover,
.idm_picture__product.--show {
.idm_picture__product:hover {
z-index: 20;
}
.idm_picture__img {
@@ -114,15 +114,18 @@ PULSE ANIMATION
.product_info {
background: var(--photo-prod-box-bg);
flex-direction: column;
padding: 1rem;
gap: 1rem;
display: none;
max-width: var(--photo-prod-box-width);
position: absolute;
width: max-content;
box-shadow: 0px 0px 10px 1px #000;
}
.product_info__container {
display: flex;
flex-direction: column;
gap: 1rem;
}
@media (min-width: 757px) {
.product_info {
padding: 1.5rem;
@@ -294,19 +297,27 @@ PULSE ANIMATION
}
}
.idm_picture__product .product_info .product_name {
:is(.idm_picture__module, .modal.--photo-prod-mobile)
.product_info
.product_name {
font-size: 1.6rem;
color: var(--photo-prod-box-text);
}
.idm_picture__product .product_info .product_name:hover {
:is(.idm_picture__module, .modal.--photo-prod-mobile)
.product_info
.product_name:hover {
color: var(--primary-color, #000) !important;
}
.idm_picture__product .product_info .product_prices {
:is(.idm_picture__module, .modal.--photo-prod-mobile)
.product_info
.product_prices {
font-size: 1.6rem;
color: var(--photo-prod-box-text);
}
.idm_picture__product .product_info .price.--main {
:is(.idm_picture__module, .modal.--photo-prod-mobile)
.product_info
.price.--main {
margin-bottom: 0;
}
@@ -320,12 +331,6 @@ PULSE ANIMATION
animation: idmShowUp 0.3s ease-in-out;
}
}
@media (max-width: 978px) {
.idm_picture__product.--show .product_info {
display: flex;
animation: idmShowUp 0.3s ease-in-out;
}
}
@keyframes idmShowUp {
from {
@@ -356,38 +361,101 @@ PULSE ANIMATION
}
}
.idm_picture__module .label_icons {
:is(.idm_picture__module, .modal.--photo-prod-mobile) .label_icons {
display: flex;
gap: 0.5rem;
position: static;
}
.idm_picture__module .product_opinions {
:is(.idm_picture__module, .modal.--photo-prod-mobile) .product_opinions {
display: flex;
color: var(--photo-prod-box-text);
}
.idm_picture__module .icon-star:not(.--active)::before {
:is(.idm_picture__module, .modal.--photo-prod-mobile)
.icon-star:not(.--active)::before {
content: "\f006";
}
.idm_picture__module .icon-star.--active {
:is(.idm_picture__module, .modal.--photo-prod-mobile) .icon-star.--active {
color: var(--opinions-star-active-color, #fac917);
}
.idm_picture__module .product_opinions__score {
:is(.idm_picture__module, .modal.--photo-prod-mobile)
.product_opinions__score {
margin-left: 1rem;
margin-right: 0.5rem;
}
.idm_picture__module .product_opinions__count::before {
:is(.idm_picture__module, .modal.--photo-prod-mobile)
.product_opinions__count::before {
content: "(";
}
.idm_picture__module .product_opinions__count::after {
:is(.idm_picture__module, .modal.--photo-prod-mobile)
.product_opinions__count::after {
content: ")";
}
.idm_picture__module :is(.add_to_basket, .add_to_basket__link) {
:is(.idm_picture__module, .modal.--photo-prod-mobile)
:is(.add_to_basket, .add_to_basket__link) {
display: flex;
justify-content: center;
margin-top: 0;
}
:is(.idm_picture__module, .modal.--photo-prod-mobile)
:is(.price.--omnibus.omnibus_price, .price.--max) {
color: var(--photo-prod-box-text);
}
:is(.idm_picture__module, .modal.--photo-prod-mobile)
.--omnibus-higher
:is(.price.--normal.--main, .price_percent) {
color: var(--color-promo-price, #cd2323);
}
.idm_picture__module .product_icon {
display: none !important;
}
@media (max-width: 978px) {
.modal.--photo-prod-mobile .product_info.--mod-init {
position: static;
display: flex;
width: 100%;
box-shadow: none;
flex-direction: column;
}
.modal.--photo-prod-mobile .modal__wrapper {
border-radius: 10px 10px 0 0;
background-color: var(--photo-prod-box-bg);
color: var(--photo-prod-box-text);
}
.modal.--photo-prod-mobile .modal__close {
position: absolute;
color: var(--photo-prod-box-text);
}
.modal.--photo-prod-mobile
.modal__close
:is(.product_name, .omnibus_price, .price.--max) {
color: var(--photo-prod-box-text);
}
.modal.--photo-prod-mobile .product_icon {
display: flex;
justify-content: center;
align-items: center;
}
}
@media (min-width: 420px) and (max-width: 978px) {
.modal.--photo-prod-mobile .product_info.--mod-init {
display: grid;
grid-template-columns: 1fr 2fr;
}
.modal.--photo-prod-mobile .product_info.--mod-init > *:not(.product_icon) {
grid-column: "2/3";
}
.modal.--photo-prod-mobile .product_info.--mod-init > .product_icon {
grid-row: "1/-1";
}
}
</style>

62
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@mui/icons-material": "^7.3.6",
"@mui/material": "^7.3.6",
"react": "^19.2.0",
"react-color": "^2.19.3",
"react-dom": "^19.2.0",
"react-hot-toast": "^2.6.0",
"react-router-dom": "^7.11.0",
@@ -1076,6 +1077,15 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@icons/material": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz",
"integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==",
"license": "MIT",
"peerDependencies": {
"react": "*"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -2999,6 +3009,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/lodash-es": {
"version": "4.17.23",
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz",
"integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==",
"license": "MIT"
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3028,6 +3050,12 @@
"yallist": "^3.0.2"
}
},
"node_modules/material-colors": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz",
"integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==",
"license": "ISC"
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -3268,6 +3296,7 @@
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"license": "MIT",
"peer": true,
"dependencies": {
"loose-envify": "^1.4.0",
"object-assign": "^4.1.1",
@@ -3300,6 +3329,24 @@
"node": ">=0.10.0"
}
},
"node_modules/react-color": {
"version": "2.19.3",
"resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz",
"integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==",
"license": "MIT",
"dependencies": {
"@icons/material": "^0.2.4",
"lodash": "^4.17.15",
"lodash-es": "^4.17.15",
"material-colors": "^1.2.1",
"prop-types": "^15.5.10",
"reactcss": "^1.2.0",
"tinycolor2": "^1.4.1"
},
"peerDependencies": {
"react": "*"
}
},
"node_modules/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
@@ -3404,6 +3451,15 @@
"react-dom": ">=16.6.0"
}
},
"node_modules/reactcss": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz",
"integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==",
"license": "MIT",
"dependencies": {
"lodash": "^4.0.1"
}
},
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
@@ -3583,6 +3639,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tinycolor2": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==",
"license": "MIT"
},
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",

View File

@@ -15,6 +15,7 @@
"@mui/icons-material": "^7.3.6",
"@mui/material": "^7.3.6",
"react": "^19.2.0",
"react-color": "^2.19.3",
"react-dom": "^19.2.0",
"react-hot-toast": "^2.6.0",
"react-router-dom": "^7.11.0",

View File

@@ -3,7 +3,6 @@ import Home from "./pages/Home";
import AppLayout from "./ui/AppLayout";
import Instruction from "./pages/Instruction";
import { Toaster } from "react-hot-toast";
import { Alert } from "@mui/material";
function App() {
return (

View File

@@ -23,7 +23,16 @@ export const DEFAULT_POINT = {
direction: Object.keys(DIRECTIONS)[0],
}; // Default point structure
export const URL_RADIO_DATA = [
{ value: "single", label: "Jedno Zdjęcie" },
{ value: "rwd", label: "Trzy Zdjęcia (RWD)" },
export const DEFAULT_COLOR_PALLETE = [
"#D9E3F0",
"#F47373",
"#697689",
"#37D67A",
"#2CCCE4",
"#555555",
"#dce775",
"#ff8a65",
"#ba68c8",
"#111111",
"#f2f2f2",
];

View File

@@ -17,6 +17,13 @@ export default function CodeBox() {
}
}, [state.urls, state.points, state.photoAlt, state.previewMode]); // reactive slices
if (
import.meta.env.MODE === "production" &&
window.location.href.includes("https://zooart6.yourtechnicaldomain.com/") &&
!window.location.search.includes("dev=test")
)
return null;
const copyCode = () => {
navigator.clipboard.writeText(code);
toast.success("Skopiowano tekst!");

View File

@@ -1,40 +1,18 @@
import styled from "@emotion/styled";
import { Button, Tab, Tabs } from "@mui/material";
import { Tab, Tabs } from "@mui/material";
import { BREAKPOINTS } from "../../../constants/rwd";
import { useSharedState } from "../../../store/useSharedState";
import { capitalizeFirstLetter } from "../../../utils/capitalizeFirstLetter";
const StyledPreviewTabsContainer = styled("div")({
display: "grid",
gridTemplateColumns: "repeat(3, 1fr)",
gap: "0.1rem",
// backgroundColor: "#060606",
});
function PreviewRWDTabs() {
const currentPreviewMode = useSharedState((state) => state.previewMode);
const setPreviewMode = useSharedState((state) => state.setPreviewMode);
const setField = useSharedState((state) => state.setField);
if (currentPreviewMode === "single") return null;
return (
// <StyledPreviewTabsContainer>
// {currentPreviewMode === "single"
// ? null
// : Object.keys(BREAKPOINTS).map((key) => (
// <Button
// key={key}
// variant="contained"
// disabled={currentPreviewMode === key}
// onClick={() => setPreviewMode(key)}
// >
// {capitalizeFirstLetter(key)}
// </Button>
// ))}
// </StyledPreviewTabsContainer>
<Tabs
value={currentPreviewMode}
onChange={(_, newVal) => setPreviewMode(newVal)}
onChange={(_, newVal) => setField("previewMode", newVal)}
variant="fullWidth"
sx={(theme) => ({
backgroundColor: theme.palette.background.header,

View File

@@ -7,10 +7,27 @@ function GeneratePreview({ preview = true }) {
const urls = useSharedState((state) => state.urls);
const uniqueId = Math.ceil(Math.random() * 100000 + 1);
const labels = useSharedState((state) => state.labels);
const opinions = useSharedState((state) => state.opinions);
const addToBasket = useSharedState((state) => state.addToBasket);
const bgColor = useSharedState((state) => state.bgColor);
const textColor = useSharedState((state) => state.textColor);
if (preview && urls[previewMode] === "") return null;
return (
<div className="idm_picture__module" id={`idm-picture-module-${uniqueId}`}>
<div
className="idm_picture__module"
id={`idm-picture-module-${uniqueId}`}
data-labels={labels && true}
data-opinions={opinions && true}
data-add-to-basket={addToBasket && true}
style={{
"--photo-mod-col-bg": bgColor,
"--photo-mod-col-text": textColor,
}}
>
{<GeneratePreviewImage preview={preview} urls={urls} />}
{<GeneratePreviewPoints preview={preview} uniqueId={uniqueId} />}
</div>

View File

@@ -1,13 +1,15 @@
import { useSharedState } from "../../../store/useSharedState";
function GeneratePreviewImage({ preview, urls }) {
const imagePrefix =
import.meta.env.MODE === "development"
? `${import.meta.env.VITE_PUBLIC_URL}/`
: "";
const previewMode = useSharedState((state) => state.previewMode);
const photoAlt = useSharedState((state) => state.photoAlt);
const imagePrefix =
import.meta.env.MODE === "development" &&
!urls[previewMode]?.includes("https://")
? `${import.meta.env.VITE_PUBLIC_URL}/`
: "";
if (preview)
return (
<img

View File

@@ -12,6 +12,9 @@ function GeneratePreviewSinglePoint({
const { positions, id } = useSharedState((state) => state.points[index]);
const previewMode = useSharedState((state) => state.previewMode);
const product = useSharedState((state) => state.products[id]);
const labels = useSharedState((state) => state.labels);
const opinions = useSharedState((state) => state.opinions);
const addToBasket = useSharedState((state) => state.addToBasket);
const setSinglePointPosition = useSharedState(
(state) => state.setSinglePointPosition,
@@ -37,63 +40,46 @@ function GeneratePreviewSinglePoint({
};
};
if (preview)
return (
<div
className="idm_picture__product"
style={{
top: `${positions[previewMode].y}%`,
left: `${positions[previewMode].x}%`,
display: positions[previewMode].hide ? "none" : "block",
}}
>
<button
className="idm_picture__product_point"
aria-describedby={prodBoxUniqueId}
aria-label="Product Info"
onPointerDown={onPointerDown}
>
+
</button>
<div
className="product_info"
id={prodBoxUniqueId}
data-id={id}
{...generatePointDataAttribbutes(previewMode)}
>
<a className="product_name" href="#">
{product ? product?.name : `Produkt ${index + 1}`}
</a>
<span className="product_price">
{product
? product?.price?.price?.gross?.formatted
: `Produkt ${index + 1}`}
</span>
</div>
</div>
);
// WARUNKI DLA STYLE/DATA ATRYBUTY
const mainContainerStyles = () => {
if (preview)
return {
top: `${positions[previewMode].y}%`,
left: `${positions[previewMode].x}%`,
display: positions[previewMode].hide ? "none" : "block",
};
if (previewMode === "single")
return { top: `${positions.single.y}%`, left: `${positions.single.x}%` };
else
return Object.keys(BREAKPOINTS).reduce((acc, key) => {
acc[`--photo-prod-point-${key}-top`] = `${positions[key].y}%`;
acc[`--photo-prod-point-${key}-left`] = `${positions[key].x}%`;
acc[`--photo-prod-point-${key}-display`] = positions[key].hide
? "none"
: "block";
return acc;
}, {});
};
const productInfoDataAttributes = () => {
if (preview || previewMode === "single")
return generatePointDataAttribbutes(previewMode);
else
return {
...generatePointDataAttribbutes("mobile"),
...generatePointDataAttribbutes("tablet"),
...generatePointDataAttribbutes("desktop"),
};
};
return (
<div
className="idm_picture__product"
style={
previewMode === "single"
? { top: `${positions.single.y}%`, left: `${positions.single.x}%` }
: Object.keys(BREAKPOINTS).reduce((acc, key) => {
acc[`--photo-prod-point-${key}-top`] = `${positions[key].y}%`;
acc[`--photo-prod-point-${key}-left`] = `${positions[key].x}%`;
acc[`--photo-prod-point-${key}-display`] = positions[key].hide
? "none"
: "block";
return acc;
}, {})
}
>
<div className="idm_picture__product" style={mainContainerStyles()}>
<button
className="idm_picture__product_point"
aria-describedby={prodBoxUniqueId}
tabIndex={-1}
aria-hidden="true"
onPointerDown={onPointerDown}
>
+
</button>
@@ -101,14 +87,52 @@ function GeneratePreviewSinglePoint({
className="product_info"
id={prodBoxUniqueId}
data-id={id}
{...(previewMode === "single"
? generatePointDataAttribbutes(previewMode)
: {
...generatePointDataAttribbutes("mobile"),
...generatePointDataAttribbutes("tablet"),
...generatePointDataAttribbutes("desktop"),
})}
></div>
{...productInfoDataAttributes()}
>
{preview && (
<div class="product_info__container">
{labels && (
<strong class="label_icons">
<span class="label --new">Nowość</span>
</strong>
)}
{opinions && (
<div class="product_opinions">
<div class="product_opinions__stars">
<i class="icon-star">*</i>
<i class="icon-star">*</i>
<i class="icon-star">*</i>
<i class="icon-star">*</i>
<i class="icon-star">*</i>
</div>
<span class="product_opinions__score">0 / 5.00 </span>
<span class="product_opinions__count">0</span>
</div>
)}
<a className="product_name" href="#">
{product ? product?.name : `Produkt ${index + 1}`}
</a>
<div class="product_prices">
<span class="price --normal --main">
{product
? product?.price?.price?.gross?.formatted
: `${index + 1}`}
</span>
</div>
{addToBasket && (
<button
class="btn --solid --medium add_to_basket__button"
tabindex="0"
data-success="Dodany"
data-error="Wystąpił błąd"
data-text="Do koszyka"
>
<span>Do koszyka</span>
</button>
)}
</div>
)}
</div>
</div>
);
}

View File

@@ -1,10 +1,12 @@
function GenerateStyle() {
return (
<style>
{`
<>
<style>
{`
.idm_picture__module{
--photo-prod-box-bg: #fff;
--photo-prod-box-text: #111;
--photo-prod-box-bg: var(--photo-mod-col-bg, #fff);
--photo-prod-box-text: var(--photo-mod-col-text, #111);
--photo-prod-point-shadow: rgba(255,255,255,.5);
--photo-prod-box-pad-top: 1em;
@@ -35,7 +37,7 @@ function GenerateStyle() {
position: absolute;
z-index: 10;
}
.idm_picture__product:hover, .idm_picture__product.--show{
.idm_picture__product:hover{
z-index: 20;
}
.idm_picture__img{
@@ -116,15 +118,18 @@ PULSE ANIMATION
.product_info{
background: var(--photo-prod-box-bg);
flex-direction: column;
padding: 1em;
gap: 1em;
display: none;
max-width: var(--photo-prod-box-width);
position: absolute;
width: max-content;
box-shadow: 0px 0px 10px 1px #000;
}
.product_info__container{
display: flex;
flex-direction: column;
gap: 1em;
}
@media (min-width: 757px){
.product_info{
padding: 1.5em;
@@ -296,19 +301,19 @@ PULSE ANIMATION
}
}
.idm_picture__product .product_info .product_name{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .product_info .product_name{
font-size: 1.6em;
color: var(--photo-prod-box-text);
}
.idm_picture__product .product_info .product_name:hover{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .product_info .product_name:hover{
color: var(--primary-color, #000)!important;
}
.idm_picture__product .product_info .product_prices{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .product_info .product_prices{
font-size: 1.6em;
color: var(--photo-prod-box-text);
}
.idm_picture__product .product_info .price.--main{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .product_info .price.--main{
margin-bottom: 0;
}
@@ -318,12 +323,6 @@ PULSE ANIMATION
animation: idmShowUp 0.3s ease-in-out;
}
}
@media(max-width: 978px){
.idm_picture__product.--show .product_info{
display: flex;
animation: idmShowUp 0.3s ease-in-out;
}
}
@keyframes idmShowUp{
from{
@@ -354,47 +353,154 @@ PULSE ANIMATION
}
}
.idm_picture__module .label_icons{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .label_icons{
display: flex;
gap: 0.5em;
position: static;
}
.idm_picture__module .product_opinions{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .product_opinions{
display: flex;
color: var(--photo-prod-box-text);
}
.idm_picture__module .icon-star:not(.--active)::before{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .icon-star:not(.--active)::before{
content: "\f006";
}
.idm_picture__module .icon-star.--active{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .icon-star.--active{
color: var(--opinions-star-active-color, #fac917);
}
.idm_picture__module .product_opinions__score{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .product_opinions__score{
margin-left: 1em;
margin-right: 0.5em;
}
.idm_picture__module .product_opinions__count::before{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .product_opinions__count::before{
content: "(";
}
.idm_picture__module .product_opinions__count::after{
:is(.idm_picture__module, .modal.--photo-prod-mobile) .product_opinions__count::after{
content: ")";
}
.idm_picture__module :is(.add_to_basket, .add_to_basket__link){
:is(.idm_picture__module, .modal.--photo-prod-mobile) :is(.add_to_basket, .add_to_basket__link){
display: flex;
justify-content: center;
margin-top: 0;
}
:is(.idm_picture__module, .modal.--photo-prod-mobile) :is(.price.--omnibus.omnibus_price, .price.--max){
color: var(--photo-prod-box-text);
}
:is(.idm_picture__module, .modal.--photo-prod-mobile) .--omnibus-higher :is(.price.--normal.--main, .price_percent){
color: var(--color-promo-price, #cd2323);
}
.idm_picture__module .product_icon{
display: none!important;
}
// MOBILE
@media (max-width: 978px){
.modal.--photo-prod-mobile .product_info.--mod-init{
position: static;
display: flex;
width: 100%;
box-shadow: none;
}
.modal.--photo-prod-mobile .modal__wrapper{
border-radius: 10px 10px 0 0;
background-color: var(--photo-prod-box-bg);
color: var(--photo-prod-box-text);
}
.modal.--photo-prod-mobile .modal__close{
position: absolute;
color: var(--photo-prod-box-text);
}
.modal.--photo-prod-mobile .modal__close :is(.product_name, .omnibus_price, .price.--max){
color: var(--photo-prod-box-text);
}
.modal.--photo-prod-mobile .product_icon{
display: flex;
justify-content: center;
align-items: center;
}
}
@media(min-width: 400px) and (max-width: 978px){
.modal.--photo-prod-mobile .product_info.--mod-init {
display: grid;
grid-template-columns: 1fr 2fr;
}
.modal.--photo-prod-mobile .product_info.--mod-init > *:not(.product_icon){
grid-column: ~"2/3";
}
.modal.--photo-prod-mobile .product_info.--mod-init > .product_icon{
grid-row: ~"1/-1"
}
}
`}
</style>
<style>
{`
.idm_picture__product_point{
cursor: grab;
cursor: grab;
}
`}
</style>
.idm_picture__product .product_info .product_prices{
font-size: 1.6em;
color: #000;
}
.label_icons > *{
font-size: 1em;
color: #fff;
background: #0090f6;
font-weight: 600;
padding: 4px 6px;
border-radius: 5px;
}
[class^="icon-"], [class*=" icon-"]{
font-family: FontAwesome;
}
.icon-star:before {
content: "\f005" / "";
}
.product_opinions{
font-size: 1.4em;
}
.btn{
background: none;
padding: 0;
border: 1px solid transparent;
display: inline-block;
text-decoration: none;
color: #E11D48;
width: 100%;
text-align: center;
cursor: pointer;
font-size: 1.2em;
padding: 0.9em 1em;
font-weight: normal;
transition: background-color 0.2s, color 0.2s, border-color 0.2s;
border-radius: 5px;
}
.btn.--solid {
background: #E11D48;
border: 1px solid #E11D48;
color: #fff;
}
.btn.--medium {
font-size: 1.4em;
padding: 1.25em 1.4em;
}
`}
</style>
</>
);
}

View File

@@ -1,35 +1,107 @@
import { useState } from "react";
import GenericBox from "../../ui/GenericBox";
import { URL_RADIO_DATA } from "./../../constants/photo";
import { useSharedState } from "../../store/useSharedState";
import GenericRadioGroup from "./../../ui/GenericRadioGroup";
import { Divider } from "@mui/material";
import {
Alert,
Divider,
FormControlLabel,
FormGroup,
Switch,
} from "@mui/material";
import ColorPickerInput from "../../ui/ColorPickerInput";
import { DEFAULT_COLOR_PALLETE } from "../../constants/photo";
function PhotoGenericOptions() {
const [urlPoint, setUrlPoint] = useState("single");
const setPreviewMode = useSharedState((state) => state.setPreviewMode);
const setField = useSharedState((state) => state.setField);
const toggleField = useSharedState((state) => state.toggleField);
const handleUrlRadioChange = (event) => {
setUrlPoint(event.target.value);
setPreviewMode(event.target.value === "rwd" ? "desktop" : "single");
const isRWD = useSharedState((state) => state.isRWD);
const labels = useSharedState((state) => state.labels);
const opinions = useSharedState((state) => state.opinions);
const addToBasket = useSharedState((state) => state.addToBasket);
const bgColor = useSharedState((state) => state.bgColor);
const textColor = useSharedState((state) => state.textColor);
const handleUrlRadioChange = (e) => {
toggleField("isRWD");
setField("previewMode", e.target.checked ? "desktop" : "single");
};
return (
<GenericBox variant="inner" title="Opcje">
<GenericRadioGroup
// <GenericBox variant="inner" title="Opcje">
<FormGroup sx={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
{/* <GenericRadioGroup
radioData={URL_RADIO_DATA}
value={urlPoint}
value={isRwd}
onChange={handleUrlRadioChange}
direction="row"
/> */}
<FormControlLabel
control={<Switch checked={isRWD} onChange={handleUrlRadioChange} />}
label="Czy wyświetlać inne zdjęcia dla innych szerokości ekranu (RWD)"
/>
<Divider />
Labelki
<Divider />
Opinie
<Divider />
Dodaj do koszyka
<Divider />
</GenericBox>
<Divider sx={{ borderColor: "#ccc" }} />
<FormControlLabel
control={
<Switch checked={labels} onChange={() => toggleField("labels")} />
}
label="Czy wyświetlać labele produktu (np Nowość, Promocja itp)"
/>
<Divider sx={{ borderColor: "#ccc" }} />
<FormControlLabel
control={
<Switch checked={opinions} onChange={() => toggleField("opinions")} />
}
label="Czy wyświetlać dane o opinii o produkcie"
/>
<Divider sx={{ borderColor: "#ccc" }} />
<FormControlLabel
control={
<Switch
checked={addToBasket}
onChange={() => toggleField("addToBasket")}
/>
}
label="Czy wyświetlać przycisk dodania do koszyka na produkcie"
/>
<Divider sx={{ borderColor: "#ccc" }} />
<div
style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "2rem" }}
>
<ColorPickerInput
color={bgColor}
onChange={(newColor) => setField("bgColor", newColor)}
label="Kolor Tła"
palette={DEFAULT_COLOR_PALLETE}
/>
<ColorPickerInput
color={textColor}
onChange={(newColor) => setField("textColor", newColor)}
label="Kolor Tekstu"
palette={DEFAULT_COLOR_PALLETE}
/>
</div>
<Divider sx={{ borderColor: "#ccc" }} />
<Alert severity="warning">
Uwaga!
<br />
Wygląd boxa produktowego na podglądzie może się różnić od tego
wrzuconego na sklep. Aplikacja nie pobiera czcionek ani kolorów ze
sklepu!
<br />
Podgląd wyświetla tylko jeden testowy label i nie wyświetla danych
omnibusa.
</Alert>
</FormGroup>
// {/* </GenericBox> */}
);
}

View File

@@ -10,7 +10,7 @@ function PhotoUrl() {
const setUrl = useSharedState((state) => state.setUrl);
const photoAlt = useSharedState((state) => state.photoAlt);
const setPhotoAlt = useSharedState((state) => state.setPhotoAlt);
const setField = useSharedState((state) => state.setField);
const handleChangeURL = ({ event, type }) => {
event.preventDefault();
@@ -29,7 +29,7 @@ function PhotoUrl() {
<Divider />
<InputField
name="Alt zdjęcia"
onChange={(e) => setPhotoAlt(e.target.value)}
onChange={(e) => setField("photoAlt", e.target.value)}
value={photoAlt}
/>
</GenericBox>

View File

@@ -1,28 +1,26 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import { ThemeProvider, CssBaseline } from '@mui/material';
import { BrowserRouter } from 'react-router-dom';
import theme from './styles/theme';
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { ThemeProvider, CssBaseline } from "@mui/material";
import { BrowserRouter } from "react-router-dom";
import theme from "./styles/theme";
// import ThemeCssVars from './styles/ThemeCssVars';
const BASENAME = import.meta.env.VITE_PUBLIC_URL || "/";
const path = window.location.pathname;
if (path.endsWith('index.html')) {
window.location.replace(BASENAME);
if (path.endsWith("index.html")) {
window.location.replace(BASENAME);
}
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<BrowserRouter basename={BASENAME}>
<ThemeProvider theme={theme}>
<CssBaseline/>
{/* <ThemeCssVars/> */}
<App />
</ThemeProvider>
</BrowserRouter>
</React.StrictMode>
)
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter basename={BASENAME}>
<ThemeProvider theme={theme}>
<CssBaseline />
{/* <ThemeCssVars/> */}
<App />
</ThemeProvider>
</BrowserRouter>
</React.StrictMode>,
);

View File

@@ -1,4 +1,5 @@
import { create } from "zustand";
import { persist } from "zustand/middleware";
import { DEFAULT_POINT } from "./../constants/photo";
// export const createBox = () => ({});
@@ -7,67 +8,116 @@ const defaultState = {
points: [{ ...DEFAULT_POINT }],
photoAlt: "Zdjęcie pokazowe",
previewMode: "single", // desktop, tablet, mobile
isRWD: false,
labels: false,
opinions: false,
addToBasket: false,
//colros
bgColor: "#fff",
textColor: "#060606",
};
export const useSharedState = create((set, get) => ({
//DATA
...defaultState,
products: {},
export const useSharedState = create(
persist(
(set, get) => ({
//DATA
...defaultState,
products: {},
//SETTERS/UPDATERS
setUrl: (type, url) =>
set((state) => ({ urls: { ...state.urls, [type]: url } })),
//SETTERS/UPDATERS
setField: (key, value) =>
set(() => ({
[key]: value,
})),
setPhotoAlt: (alt) => set({ photoAlt: alt }),
setPoints: (points) => set({ points }),
setPointsLength: (length) => set({ pointsLength: length }),
setPreviewMode: (mode) => set({ previewMode: mode }),
toggleField: (key) =>
set((state) => ({
[key]: !state[key],
})),
// Update a single point by ID
setSinglePoint: (id, newData) =>
set((state) => ({
points: state.points.map((p, index) =>
index === id ? { ...p, ...newData } : p
),
})),
// setPhotoAlt: (alt) => set({ photoAlt: alt }),
// setPreviewMode: (mode) => set({ previewMode: mode }),
setSinglePointPosition: (id, brName, positionName, newValue) =>
set((state) => ({
points: state.points.map((p, index) =>
index === id
? {
...p,
positions: {
...p.positions,
[brName]: {
...p.positions[brName],
[positionName]: newValue,
},
},
}
: p
),
})),
/* =========
URLS
========= */
setUrl: (type, url) =>
set((state) => ({ urls: { ...state.urls, [type]: url } })),
// Add a point AND increment pointsLength
addSinglePoint: (point) =>
set((state) => ({
points: [...state.points, point],
})),
/* =========
POINTS
========= */
// Remove a point AND decrement pointsLength
removeSinglePoint: (id) =>
set((state) => ({
points: state.points.filter((p, index) => index !== id),
})),
setPoints: (points) => set({ points }),
// Update a single point by ID
setSinglePoint: (id, newData) =>
set((state) => ({
points: state.points.map((p, index) =>
index === id ? { ...p, ...newData } : p,
),
})),
clearAll: () => set(() => ({ ...defaultState })),
setSinglePointPosition: (id, brName, positionName, newValue) =>
set((state) => ({
points: state.points.map((p, index) =>
index === id
? {
...p,
positions: {
...p.positions,
[brName]: {
...p.positions[brName],
[positionName]: newValue,
},
},
}
: p,
),
})),
addProduct: (prod) =>
set((state) => ({
products: {
...state.products,
[prod.id]: prod,
},
})),
}));
// Add a point AND increment pointsLength
addSinglePoint: (point) =>
set((state) => ({
points: [...state.points, point],
})),
// Remove a point AND decrement pointsLength
removeSinglePoint: (id) =>
set((state) => ({
points: state.points.filter((p, index) => index !== id),
})),
/* =========
PRODUCTS
========= */
addProduct: (prod) =>
set((state) => ({
products: {
...state.products,
[prod.id]: prod,
},
})),
/* =========
RESET
========= */
clearAll: () => set(() => ({ ...defaultState })),
}),
{
name: "photo-generic-options",
partialize: (state) => ({
labels: state.labels,
opinions: state.opinions,
addToBasket: state.addToBasket,
previewMode: state.previewMode,
isRWD: state.isRWD,
bgColor: state.bgColor,
textColor: state.textColor,
}),
},
),
);

View File

@@ -0,0 +1,71 @@
import { useEffect, useRef, useState } from "react";
import { BlockPicker } from "react-color";
import { Box, ButtonBase, Typography } from "@mui/material";
function ColorPickerButton({ color, onChange, label = "Color", palette = [] }) {
const [open, setOpen] = useState(false);
const wrapperRef = useRef(null);
useEffect(() => {
function handleClickOutside(e) {
if (!wrapperRef.current?.contains(e.target)) {
setOpen(false);
}
}
if (open) {
document.addEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [open]);
return (
<Box
ref={wrapperRef}
sx={{ position: "relative", display: "inline-block" }}
>
{label && (
<Typography variant="caption" sx={{ display: "block", mb: 0.5 }}>
{label}
</Typography>
)}
<ButtonBase
onClick={() => setOpen((v) => !v)}
sx={{
width: "100%",
height: 32,
borderRadius: 1,
border: "1px solid",
borderColor: "divider",
backgroundColor: color,
}}
/>
{open && (
<Box
className="color-picker block-picker"
sx={{
position: "absolute",
top: "100%",
left: "50%",
mt: 1,
zIndex: 1300,
transform: "translateX(-50%)",
}}
>
<BlockPicker
color={color}
colors={palette}
onChange={(c) => onChange(c.hex)}
/>
</Box>
)}
</Box>
);
}
export default ColorPickerButton;