diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0a37a01..f1e57c9 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,7 +1,17 @@ { "permissions": { "allow": [ - "WebSearch" + "WebSearch", + "Bash(git add:*)", + "Bash(git commit:*)", + "Bash(find:*)", + "Bash(curl:*)", + "WebFetch(domain:snipcart.com)", + "Bash(grep:*)", + "Bash(npm run build:*)", + "Bash(php test-shopify.php:*)", + "WebFetch(domain:getkirby.com)", + "WebFetch(domain:forum.getkirby.com)" ] } } diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml index 5ea23f5..87654ef 100644 --- a/.forgejo/workflows/deploy.yml +++ b/.forgejo/workflows/deploy.yml @@ -26,7 +26,7 @@ jobs: set ftp:ssl-allow no open -u $USERNAME,$PASSWORD $PRODUCTION_HOST mirror --reverse --verbose --ignore-time --parallel=10 -x local/ assets assets - mirror --reverse --verbose --ignore-time --parallel=10 -x accounts/ -x cache/ -x sessions/ -x header.php site site + mirror --reverse --verbose --ignore-time --parallel=10 -x accounts/ -x cache/ -x sessions/ site site mirror --reverse --verbose --ignore-time --parallel=10 kirby kirby mirror --reverse --verbose --ignore-time --parallel=10 vendor vendor quit diff --git a/.gitignore b/.gitignore index 975043c..d2b4a11 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,9 @@ Icon # Guide d'intégration (contient des informations sensibles) # --------------- -GUIDE-INTEGRATION-MONDIAL-RELAY.md \ No newline at end of file +GUIDE-INTEGRATION-MONDIAL-RELAY.md + +# Claude settings +# --------------- +.claude +/.claude/* \ No newline at end of file diff --git a/assets/css/components/_shopify-buy-button.scss b/assets/css/components/_shopify-buy-button.scss new file mode 100644 index 0000000..ca48dde --- /dev/null +++ b/assets/css/components/_shopify-buy-button.scss @@ -0,0 +1,68 @@ +.product-purchase { + margin-top: 2rem; +} + +.product-stock-info { + margin-bottom: 1rem; +} + +.stock-status { + font-size: 0.9rem; + font-weight: 600; + margin: 0; +} + +.stock-status.in-stock { + color: #00cc00; +} + +.stock-status.low-stock { + color: #ff9900; +} + +.stock-status.out-of-stock { + color: #ff3333; +} + +.btn-add-to-cart { + font-family: "Open Sans", sans-serif; + font-weight: bold; + font-size: 1rem; + color: #000000; + background-color: #00ff00; + border: none; + border-radius: 40px; + padding: 12px 34px; + cursor: pointer; + transition: background-color 0.3s ease; + width: 100%; + max-width: 300px; +} + +.btn-add-to-cart:hover:not(:disabled) { + background-color: #00e600; +} + +.btn-add-to-cart:focus { + outline: 2px solid #00e600; + outline-offset: 2px; +} + +.btn-add-to-cart:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-add-to-cart.success { + background-color: #00cc00; +} + +.btn-add-to-cart.error { + background-color: #ff3333; + color: #ffffff; +} + +.btn-add-to-cart.out-of-stock { + background-color: #cccccc; + color: #666666; +} diff --git a/assets/css/components/_shopify-cart-drawer.scss b/assets/css/components/_shopify-cart-drawer.scss new file mode 100644 index 0000000..265a436 --- /dev/null +++ b/assets/css/components/_shopify-cart-drawer.scss @@ -0,0 +1,258 @@ +/* Cart Drawer Styles */ +.cart-drawer { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 9999; + pointer-events: none; + opacity: 0; + transition: opacity 0.3s ease; + color: #000; + + &.is-open { + pointer-events: auto; + opacity: 1; + + .cart-drawer__panel { + transform: translateX(0); + } + } + + &__overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + cursor: pointer; + } + + &__panel { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 100%; + max-width: 420px; + background-color: #ffffff; + box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15); + display: flex; + flex-direction: column; + transform: translateX(100%); + transition: transform 0.3s ease; + + @media (max-width: 768px) { + max-width: 100%; + } + } + + &__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem; + border-bottom: 1px solid #e0e0e0; + + h3 { + margin: 0; + font-size: 1.5rem; + font-weight: bold; + } + } + + &__close { + background: none; + border: none; + cursor: pointer; + padding: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.2s; + + &:hover { + opacity: 0.7; + } + + svg { + stroke: #000; + } + } + + &__content { + flex: 1; + overflow-y: auto; + padding: 1.5rem; + + &.is-loading { + opacity: 0.5; + pointer-events: none; + } + } + + &__empty { + text-align: center; + padding: 3rem 1rem; + color: #666; + + &.hidden { + display: none; + } + } + + &__items { + display: flex; + flex-direction: column; + gap: 1rem; + + &.hidden { + display: none; + } + } + + &__footer { + border-top: 1px solid #e0e0e0; + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1rem; + } + + &__total { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 1.125rem; + font-weight: bold; + + &-label { + color: #000; + } + + &-amount { + color: #000; + font-size: 1.25rem; + } + } + + &__checkout-btn { + width: 100%; + font-family: "Open Sans", sans-serif; + font-weight: bold; + font-size: 1rem; + color: #000000; + background-color: #00ff00; + border: none; + border-radius: 40px; + padding: 14px 34px; + cursor: pointer; + transition: background-color 0.3s ease; + + &:hover:not(:disabled) { + background-color: #00e600; + } + + &:disabled { + opacity: 0.6; + cursor: not-allowed; + } + } +} + +// Cart Item +.cart-item { + display: flex; + gap: 1rem; + padding: 1rem; + border: 1px solid #e0e0e0; + border-radius: 8px; + + &__image { + width: 80px; + height: 80px; + object-fit: cover; + border-radius: 4px; + flex-shrink: 0; + } + + &__details { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.5rem; + } + + &__title { + font-weight: 600; + margin: 0; + font-size: 1rem; + } + + &__variant { + font-size: 0.875rem; + color: #666; + margin: 0; + } + + &__price { + font-weight: bold; + color: #000; + } + + &__quantity { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: auto; + } + + &__qty-btn { + width: 28px; + height: 28px; + border: 1px solid #000; + background: #fff; + color: #000; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; + font-weight: bold; + transition: all 0.2s; + + &:hover:not(:disabled) { + background-color: #000; + color: #fff; + } + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + } + } + + &__qty-value { + min-width: 30px; + text-align: center; + font-weight: 600; + } + + &__remove { + background: none; + border: none; + color: #ff3333; + cursor: pointer; + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + text-decoration: underline; + align-self: flex-start; + + &:hover { + color: #cc0000; + } + } +} diff --git a/assets/css/components/_text.scss b/assets/css/components/_text.scss index 80f55dd..357fe64 100644 --- a/assets/css/components/_text.scss +++ b/assets/css/components/_text.scss @@ -1,7 +1,7 @@ [data-template="subscription-newsletter"], [data-template="thanks"], [data-template="support"], -[data-template="store"] { +[data-template="home"] { .p__baseline-big { font-family: var(--title); font-size: var(--fs-big); diff --git a/assets/css/partials/_site-header.scss b/assets/css/partials/_site-header.scss index cf92821..5e80fc5 100644 --- a/assets/css/partials/_site-header.scss +++ b/assets/css/partials/_site-header.scss @@ -43,14 +43,20 @@ } } - .header-left, - .header-right { + .header-left { width: 90px; display: flex; align-items: center; justify-content: flex-end; } + .header-right { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 1rem; + } + .header-center { display: flex; flex-direction: column; @@ -75,4 +81,40 @@ color: var(--color-txt); } } + + .header-cart-btn { + font-family: var(--font); + background: none; + border: none; + cursor: pointer; + font-size: 0.875rem; + text-transform: uppercase; + color: var(--color-txt); + padding: 0; + line-height: 1; + display: flex; + align-items: center; + gap: 0.25rem; + transition: opacity 0.2s; + + &:hover { + opacity: 0.7; + } + } + + .header-cart-count { + font-weight: normal; + + &:empty { + display: none; + } + + &:not(:empty)::before { + content: "("; + } + + &:not(:empty)::after { + content: ")"; + } + } } diff --git a/assets/css/style.css b/assets/css/style.css index bb34132..90a36fa 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -402,7 +402,7 @@ main { [data-template=subscription-newsletter] .p__baseline-big, [data-template=thanks] .p__baseline-big, [data-template=support] .p__baseline-big, -[data-template=store] .p__baseline-big { +[data-template=home] .p__baseline-big { font-family: var(--title); font-size: var(--fs-big); font-weight: var(--fw-bold); @@ -413,14 +413,14 @@ main { [data-template=subscription-newsletter] .p__baseline-big strong, [data-template=thanks] .p__baseline-big strong, [data-template=support] .p__baseline-big strong, -[data-template=store] .p__baseline-big strong { +[data-template=home] .p__baseline-big strong { font-weight: var(--fw-bolf); color: var(--color-accent); } [data-template=subscription-newsletter] .p__baseline-big .link-don, [data-template=thanks] .p__baseline-big .link-don, [data-template=support] .p__baseline-big .link-don, -[data-template=store] .p__baseline-big .link-don { +[data-template=home] .p__baseline-big .link-don { display: block; color: var(--color-accent); text-decoration: none; @@ -428,7 +428,7 @@ main { [data-template=subscription-newsletter] .p__baseline-big .link-don:hover, [data-template=thanks] .p__baseline-big .link-don:hover, [data-template=support] .p__baseline-big .link-don:hover, -[data-template=store] .p__baseline-big .link-don:hover { +[data-template=home] .p__baseline-big .link-don:hover { -webkit-text-decoration: underline 2px; text-decoration: underline 2px; text-underline-offset: 4px; @@ -436,7 +436,7 @@ main { [data-template=subscription-newsletter] .p__baseline, [data-template=thanks] .p__baseline, [data-template=support] .p__baseline, -[data-template=store] .p__baseline { +[data-template=home] .p__baseline { font-size: var(--fs-medium); font-weight: var(--fw-medium); line-height: 1.1; @@ -447,7 +447,7 @@ main { [data-template=subscription-newsletter] .p__baseline, [data-template=thanks] .p__baseline, [data-template=support] .p__baseline, - [data-template=store] .p__baseline { + [data-template=home] .p__baseline { text-align: center; margin: var(--spacing) 0; } @@ -455,7 +455,7 @@ main { [data-template=subscription-newsletter] .p__details, [data-template=thanks] .p__details, [data-template=support] .p__details, -[data-template=store] .p__details { +[data-template=home] .p__details { font-size: var(--fs-small); margin-bottom: 0.5em; color: var(--grey-400); @@ -463,7 +463,7 @@ main { [data-template=subscription-newsletter] .section__heading, [data-template=thanks] .section__heading, [data-template=support] .section__heading, -[data-template=store] .section__heading { +[data-template=home] .section__heading { font-size: var(--fs-normal); font-weight: var(--fw-medium); line-height: 1; @@ -477,8 +477,8 @@ main { [data-template=thanks] ol, [data-template=support] ul, [data-template=support] ol, -[data-template=store] ul, -[data-template=store] ol { +[data-template=home] ul, +[data-template=home] ol { margin-left: 3ch; margin-bottom: 0.5em; } @@ -524,13 +524,18 @@ main { #site-header.is-shrinked .site-title { width: 80px !important; } -#site-header .header-left, -#site-header .header-right { +#site-header .header-left { width: 90px; display: flex; align-items: center; justify-content: flex-end; } +#site-header .header-right { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 1rem; +} #site-header .header-center { display: flex; flex-direction: column; @@ -553,6 +558,36 @@ main { #site-header #toggle-lang li.is-selected { color: var(--color-txt); } +#site-header .header-cart-btn { + font-family: var(--font); + background: none; + border: none; + cursor: pointer; + font-size: 0.875rem; + text-transform: uppercase; + color: var(--color-txt); + padding: 0; + line-height: 1; + display: flex; + align-items: center; + gap: 0.25rem; + transition: opacity 0.2s; +} +#site-header .header-cart-btn:hover { + opacity: 0.7; +} +#site-header .header-cart-count { + font-weight: normal; +} +#site-header .header-cart-count:empty { + display: none; +} +#site-header .header-cart-count:not(:empty)::before { + content: "("; +} +#site-header .header-cart-count:not(:empty)::after { + content: ")"; +} #site-footer { background-color: black; @@ -940,36 +975,36 @@ body.is-fullscreen { overflow: hidden; } -[data-template=store] .p__baseline-big { +[data-template=home] .p__baseline-big { margin-top: calc(var(--spacing) * 2); } -[data-template=store] #store__container { +[data-template=home] #store__container { margin-top: calc(var(--spacing) * 2); margin-bottom: calc(var(--spacing) * 4); width: 100%; max-width: 1000px; } -[data-template=store] #store__container .store__product { +[data-template=home] #store__container .store__product { position: relative; } -[data-template=store] #store__container .store__product figure { +[data-template=home] #store__container .store__product figure { aspect-ratio: 4/3; background-color: var(--color-bg); background-color: var(--data-bg); margin-bottom: calc(var(--spacing) * 0.5); overflow: hidden; } -[data-template=store] #store__container .store__product img { +[data-template=home] #store__container .store__product img { width: 100%; height: 100%; -o-object-fit: contain; object-fit: contain; transition: var(--curve) 0.5s; } -[data-template=store] #store__container .store__product a { +[data-template=home] #store__container .store__product a { text-decoration: none; } -[data-template=store] #store__container .store__product .link-block { +[data-template=home] #store__container .store__product .link-block { display: block; height: 100%; width: 100%; @@ -978,23 +1013,23 @@ body.is-fullscreen { left: 0; cursor: pointer; } -[data-template=store] #store__container .store__product:hover figure { +[data-template=home] #store__container .store__product:hover figure { overflow: hidden; } -[data-template=store] #store__container .store__product:hover img { +[data-template=home] #store__container .store__product:hover img { transform: scale(1.05); } -[data-template=store] #store__container .store__product:hover .line-1 { +[data-template=home] #store__container .store__product:hover .line-1 { text-decoration: underline; } @media screen and (max-width: 720px) { - [data-template=store] #store__container .store__product { + [data-template=home] #store__container .store__product { margin-top: calc(var(--spacing) * 1.5); margin-bottom: calc(var(--spacing) * 0.5); } } @media screen and (min-width: 720px) { - [data-template=store] #store__container { + [data-template=home] #store__container { display: grid; grid-template-columns: repeat(6, 1fr); -moz-column-gap: calc(var(--padding-body) * 0.75); @@ -1003,11 +1038,11 @@ body.is-fullscreen { margin-left: auto; margin-right: auto; } - [data-template=store] #store__container .store__product { + [data-template=home] #store__container .store__product { grid-column: span 2; } - [data-template=store] #store__container .store__product:nth-of-type(1), - [data-template=store] #store__container .store__product:nth-of-type(2) { + [data-template=home] #store__container .store__product:nth-of-type(1), + [data-template=home] #store__container .store__product:nth-of-type(2) { grid-column: span 3; } } @@ -1019,11 +1054,8 @@ body.is-fullscreen { margin-right: auto; } -.section__product, -.store__nav { - max-width: 1200px; - margin-left: auto; - margin-right: auto; +.product-content { + display: contents; } .store__nav { @@ -1035,11 +1067,17 @@ body.is-fullscreen { .store__nav a { text-decoration: none; } +.store__nav a::before { + content: "← "; +} .store__nav a:hover { text-decoration: underline; } -.store__nav a::before { - content: "← "; +@media screen and (max-width: 720px) { + .store__nav a { + padding-top: 0; + font-size: var(--fs-small); + } } .section__product .details ul { @@ -1048,122 +1086,7 @@ body.is-fullscreen { .section__product .details ul li { padding-bottom: 0.2em; } - -.product-options__list { - list-style: none; - display: flex; - gap: 2ch; -} -.product-options__list li { - position: relative; -} -.product-options__list li input[type=radio] { - position: fixed; - opacity: 0; - pointer-events: none; -} -.product-options__list li label { - font-family: var(--title); - font-size: var(--fs-normal); - height: 4ch; - width: 4ch; - border-radius: 50%; - border: var(--border); - border-color: transparent; - display: flex; - align-items: center; - justify-content: center; - padding-top: 0px; - cursor: pointer; -} -.product-options__list li input[type=radio]:checked + label { - border-color: var(--color-txt); -} -.product-options__list li input[type=radio]:not(:checked) + label:hover { - border-color: var(--grey-600); - background-color: var(--grey-800); -} - -.product-gallery { - position: relative; - aspect-ratio: 4/3; -} -.product-gallery .swiper-slide { - width: 100%; -} -.product-gallery .swiper-slide figure { - aspect-ratio: 4/3; - width: 100%; - height: 100%; -} -.product-gallery .swiper-slide figure img { - width: 100%; - height: 100%; - -o-object-fit: contain; - object-fit: contain; -} -.product-gallery .swiper-button-prev, -.product-gallery .swiper-button-next { - color: var(--color-txt); - width: 20px; - height: 20px; -} -.product-gallery .swiper-button-prev:after, -.product-gallery .swiper-button-next:after { - font-size: 20px; - font-weight: bold; -} -.product-gallery .swiper-button-prev:hover, -.product-gallery .swiper-button-next:hover { - opacity: 0.7; -} -.product-gallery .swiper-pagination { - position: relative; - margin-top: calc(var(--spacing) * 0.5); - bottom: 0; -} -.product-gallery .swiper-pagination .swiper-pagination-bullet { - width: 8px; - height: 8px; - background: var(--grey-600); - opacity: 0.5; - transition: opacity 0.3s; -} -.product-gallery .swiper-pagination .swiper-pagination-bullet:hover { - opacity: 0.7; -} -.product-gallery .swiper-pagination .swiper-pagination-bullet-active { - background: var(--color-txt); - opacity: 1; -} - -.hero { - margin-bottom: calc(var(--spacing) * 1); - padding: calc(var(--spacing) * 0.5) 0; - border-top: var(--border-light); - border-bottom: var(--border-light); -} -.hero .p__baseline-big { - margin: 0; - text-align: left; -} - -.add-to-cart { - margin: 0; - border-bottom: var(--border-light); - padding: calc(var(--spacing) * 0.5) 0; -} - -.product-options { - border-bottom: var(--border-light); - padding: calc(var(--spacing) * 0.25) 0; -} - @media screen and (max-width: 720px) { - .store__nav a { - padding-top: 0; - font-size: var(--fs-small); - } .section__product { display: flex; flex-direction: column; @@ -1202,7 +1125,7 @@ body.is-fullscreen { } } @media screen and (min-width: 720px) { - .section__product { + .section__product .product-content { display: grid; grid-template-columns: 1fr 1fr; gap: calc(var(--padding-body) * 2); @@ -1221,10 +1144,123 @@ body.is-fullscreen { display: flex; flex-direction: column; } +} + +.product-options { + border-bottom: var(--border-light); + padding: calc(var(--spacing) * 0.25) 0; +} + +.product-options__list { + list-style: none; + display: flex; + gap: 2ch; +} +.product-options__list li { + position: relative; +} +.product-options__list li input[type=radio] { + position: fixed; + opacity: 0; + pointer-events: none; +} +.product-options__list li input[type=radio]:checked + label { + border-color: var(--color-txt); +} +.product-options__list li input[type=radio]:not(:checked) + label:hover { + border-color: var(--grey-600); + background-color: var(--grey-800); +} +.product-options__list li label { + font-family: var(--title); + font-size: var(--fs-normal); + height: 4ch; + width: 4ch; + border-radius: 50%; + border: var(--border); + border-color: transparent; + display: flex; + align-items: center; + justify-content: center; + padding-top: 0px; + cursor: pointer; +} + +.product-gallery { + position: relative; + aspect-ratio: 4/3; +} +.product-gallery .swiper-slide { + width: 100%; +} +.product-gallery .swiper-slide figure { + aspect-ratio: 4/3; + width: 100%; + height: 100%; +} +.product-gallery .swiper-slide figure img { + width: 100%; + height: 100%; + -o-object-fit: contain; + object-fit: contain; +} +@media screen and (min-width: 720px) { .product-gallery .swiper-slide figure { width: calc(100% - 60px); } } +.product-gallery .swiper-button-prev, +.product-gallery .swiper-button-next { + color: var(--color-txt); + width: 20px; + height: 20px; +} +.product-gallery .swiper-button-prev:after, +.product-gallery .swiper-button-next:after { + font-size: 20px; + font-weight: bold; +} +.product-gallery .swiper-button-prev:hover, +.product-gallery .swiper-button-next:hover { + opacity: 0.7; +} +.product-gallery .swiper-pagination { + position: relative; + margin-top: calc(var(--spacing) * 0.5); + bottom: 0; +} +.product-gallery .swiper-pagination .swiper-pagination-bullet { + width: 8px; + height: 8px; + background: var(--grey-600); + opacity: 0.5; + transition: opacity 0.3s; +} +.product-gallery .swiper-pagination .swiper-pagination-bullet:hover { + opacity: 0.7; +} +.product-gallery .swiper-pagination .swiper-pagination-bullet.swiper-pagination-bullet-active { + background: var(--color-txt); + opacity: 1; +} + +.hero { + margin-bottom: calc(var(--spacing) * 1); + padding: calc(var(--spacing) * 0.5) 0; + border-top: var(--border-light); + border-bottom: var(--border-light); +} +.hero .p__baseline-big { + margin: 0; + text-align: left; +} + +.add-to-cart { + margin: 0; + border-bottom: var(--border-light); + padding: calc(var(--spacing) * 0.5) 0; +} + [data-template=thanks] .thanks-page { min-height: 60vh; display: flex; @@ -1232,32 +1268,325 @@ body.is-fullscreen { justify-content: center; padding: calc(var(--spacing) * 4) var(--spacing); } -[data-template=thanks] .thanks-content { +[data-template=thanks] .thanks-page .thanks-content { text-align: center; max-width: 600px; + display: flex; + flex-direction: column; + align-items: center; } -[data-template=thanks] .thanks-content h1 { - font-size: var(--fs-x-big); +[data-template=thanks] .thanks-page .thanks-content h1 { margin-bottom: calc(var(--spacing) * 2); } -[data-template=thanks] .thanks-content .thanks-message { - font-size: var(--fs-big); - margin-bottom: calc(var(--spacing) * 3); - line-height: 1.6; +[data-template=thanks] .thanks-page .thanks-content .thanks-message { + font-size: var(--fs-medium); + line-height: 1.1; } -[data-template=thanks] .thanks-content .thanks-message p { +[data-template=thanks] .thanks-page .thanks-content .thanks-message p { margin-bottom: var(--spacing); } -[data-template=thanks] .thanks-content .thanks-actions { - margin-top: calc(var(--spacing) * 3); +[data-template=thanks] .thanks-page .thanks-content .thanks-actions { + width: -moz-max-content; + width: max-content; } [data-template=thanks] #site-footer { border-top: none; margin-top: calc(var(--spacing) * 4); } -.snipcart-modal__container { - z-index: 1000; +.product-purchase { + margin-top: 2rem; +} + +.product-stock-info { + margin-bottom: 1rem; +} + +.stock-status { + font-size: 0.9rem; + font-weight: 600; + margin: 0; +} + +.stock-status.in-stock { + color: #00cc00; +} + +.stock-status.low-stock { + color: #ff9900; +} + +.stock-status.out-of-stock { + color: #ff3333; +} + +.btn-add-to-cart { + font-family: "Open Sans", sans-serif; + font-weight: bold; + font-size: 1rem; + color: #000000; + background-color: #00ff00; + border: none; + border-radius: 40px; + padding: 12px 34px; + cursor: pointer; + transition: background-color 0.3s ease; + width: 100%; + max-width: 300px; +} + +.btn-add-to-cart:hover:not(:disabled) { + background-color: #00e600; +} + +.btn-add-to-cart:focus { + outline: 2px solid #00e600; + outline-offset: 2px; +} + +.btn-add-to-cart:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-add-to-cart.success { + background-color: #00cc00; +} + +.btn-add-to-cart.error { + background-color: #ff3333; + color: #ffffff; +} + +.btn-add-to-cart.out-of-stock { + background-color: #cccccc; + color: #666666; +} + +/* Cart Drawer Styles */ +.cart-drawer { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 9999; + pointer-events: none; + opacity: 0; + transition: opacity 0.3s ease; + color: #000; +} +.cart-drawer.is-open { + pointer-events: auto; + opacity: 1; +} +.cart-drawer.is-open .cart-drawer__panel { + transform: translateX(0); +} +.cart-drawer__overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + cursor: pointer; +} +.cart-drawer__panel { + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 100%; + max-width: 420px; + background-color: #ffffff; + box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15); + display: flex; + flex-direction: column; + transform: translateX(100%); + transition: transform 0.3s ease; +} +@media (max-width: 768px) { + .cart-drawer__panel { + max-width: 100%; + } +} +.cart-drawer__header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem; + border-bottom: 1px solid #e0e0e0; +} +.cart-drawer__header h3 { + margin: 0; + font-size: 1.5rem; + font-weight: bold; +} +.cart-drawer__close { + background: none; + border: none; + cursor: pointer; + padding: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.2s; +} +.cart-drawer__close:hover { + opacity: 0.7; +} +.cart-drawer__close svg { + stroke: #000; +} +.cart-drawer__content { + flex: 1; + overflow-y: auto; + padding: 1.5rem; +} +.cart-drawer__content.is-loading { + opacity: 0.5; + pointer-events: none; +} +.cart-drawer__empty { + text-align: center; + padding: 3rem 1rem; + color: #666; +} +.cart-drawer__empty.hidden { + display: none; +} +.cart-drawer__items { + display: flex; + flex-direction: column; + gap: 1rem; +} +.cart-drawer__items.hidden { + display: none; +} +.cart-drawer__footer { + border-top: 1px solid #e0e0e0; + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1rem; +} +.cart-drawer__total { + display: flex; + justify-content: space-between; + align-items: center; + font-size: 1.125rem; + font-weight: bold; +} +.cart-drawer__total-label { + color: #000; +} +.cart-drawer__total-amount { + color: #000; + font-size: 1.25rem; +} +.cart-drawer__checkout-btn { + width: 100%; + font-family: "Open Sans", sans-serif; + font-weight: bold; + font-size: 1rem; + color: #000000; + background-color: #00ff00; + border: none; + border-radius: 40px; + padding: 14px 34px; + cursor: pointer; + transition: background-color 0.3s ease; +} +.cart-drawer__checkout-btn:hover:not(:disabled) { + background-color: #00e600; +} +.cart-drawer__checkout-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.cart-item { + display: flex; + gap: 1rem; + padding: 1rem; + border: 1px solid #e0e0e0; + border-radius: 8px; +} +.cart-item__image { + width: 80px; + height: 80px; + -o-object-fit: cover; + object-fit: cover; + border-radius: 4px; + flex-shrink: 0; +} +.cart-item__details { + flex: 1; + display: flex; + flex-direction: column; + gap: 0.5rem; +} +.cart-item__title { + font-weight: 600; + margin: 0; + font-size: 1rem; +} +.cart-item__variant { + font-size: 0.875rem; + color: #666; + margin: 0; +} +.cart-item__price { + font-weight: bold; + color: #000; +} +.cart-item__quantity { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: auto; +} +.cart-item__qty-btn { + width: 28px; + height: 28px; + border: 1px solid #000; + background: #fff; + color: #000; + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; + font-weight: bold; + transition: all 0.2s; +} +.cart-item__qty-btn:hover:not(:disabled) { + background-color: #000; + color: #fff; +} +.cart-item__qty-btn:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.cart-item__qty-value { + min-width: 30px; + text-align: center; + font-weight: 600; +} +.cart-item__remove { + background: none; + border: none; + color: #ff3333; + cursor: pointer; + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + text-decoration: underline; + align-self: flex-start; +} +.cart-item__remove:hover { + color: #cc0000; } [data-template=subscription-newsletter] main { diff --git a/assets/css/style.css.map b/assets/css/style.css.map index 9cd0665..abb3324 100644 --- a/assets/css/style.css.map +++ b/assets/css/style.css.map @@ -1 +1 @@ -{"version":3,"sources":["style.css","base/_var.scss","base/_body.scss","components/_nav-tabs.scss","components/_btn--default.scss","components/_btn--don.scss","components/_form-newsletter.scss","components/_gauge.scss","components/_text.scss","partials/_site-header.scss","partials/_site-footer.scss","template/support/_layout.scss","template/support/_section--donation.scss","template/support/_section--comments.scss","template/support/_section--questions.scss","template/support/_section--video.scss","template/shop/_layout.scss","template/shop/_section--product.scss","template/shop/_thanks.scss","template/shop/_snipcart.scss","template/subscription-newsletter/_layout.scss"],"names":[],"mappings":"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","file":"style.css"} \ No newline at end of file +{"version":3,"sources":["style.css","base/_var.scss","base/_body.scss","components/_nav-tabs.scss","components/_btn--default.scss","components/_btn--don.scss","components/_form-newsletter.scss","components/_gauge.scss","components/_text.scss","partials/_site-header.scss","partials/_site-footer.scss","template/support/_layout.scss","template/support/_section--donation.scss","template/support/_section--comments.scss","template/support/_section--questions.scss","template/support/_section--video.scss","template/shop/_layout.scss","template/shop/_section--product.scss","template/shop/_thanks.scss","components/_shopify-buy-button.scss","components/_shopify-cart-drawer.scss","template/subscription-newsletter/_layout.scss"],"names":[],"mappings":"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","file":"style.css"} \ No newline at end of file diff --git a/assets/css/style.scss b/assets/css/style.scss index 9687a73..334ba35 100644 --- a/assets/css/style.scss +++ b/assets/css/style.scss @@ -22,6 +22,7 @@ @import "template/shop/layout"; @import "template/shop/section--product"; @import "template/shop/thanks"; -@import "template/shop/snipcart"; +@import "components/shopify-buy-button.scss"; +@import "components/shopify-cart-drawer.scss"; @import "template/subscription-newsletter/layout"; diff --git a/assets/css/template/shop/_layout.scss b/assets/css/template/shop/_layout.scss index 22685bb..0f7f984 100644 --- a/assets/css/template/shop/_layout.scss +++ b/assets/css/template/shop/_layout.scss @@ -1,6 +1,4 @@ -[data-template="store"] { - - +[data-template="home"] { .p__baseline-big { margin-top: calc(var(--spacing) * 2); } diff --git a/assets/css/template/shop/_section--product.scss b/assets/css/template/shop/_section--product.scss index 82790ac..33573cb 100644 --- a/assets/css/template/shop/_section--product.scss +++ b/assets/css/template/shop/_section--product.scss @@ -1,12 +1,3 @@ -.section__product, -.store__nav{ - max-width: 1200px; - margin-left: auto; - margin-right: auto; -} - - - .section__product, .store__nav { max-width: 1200px; @@ -14,9 +5,9 @@ margin-right: auto; } - - - +.product-content { + display: contents; +} .store__nav { padding-top: calc(var(--spacing) * 1); @@ -27,174 +18,35 @@ a { text-decoration: none; + &::before { + content: "← "; + } + &:hover { text-decoration: underline; } } - a::before { - content: "← "; - } -} - - - -.section__product .details { - // margin-bottom: calc(var(--spacing) * 2); - - ul{ - margin-left: 2ch; - li{ - padding-bottom: 0.2em; + @media #{$small} { + a { + padding-top: 0; + font-size: var(--fs-small); } } } +.section__product { + .details { + ul { + margin-left: 2ch; -.product-options__list { - list-style: none; - display: flex; - gap: 2ch; - li { - position: relative; - - input[type="radio"] { - position: fixed; - opacity: 0; - pointer-events: none; - } - - label { - font-family: var(--title); - font-size: var(--fs-normal); - height: 4ch; - width: 4ch; - border-radius: 50%; - border: var(--border); - border-color: transparent; - display: flex; - align-items: center; - justify-content: center; - padding-top: 0px; - cursor: pointer; - } - - input[type="radio"]:checked + label { - border-color: var(--color-txt); - } - - input[type="radio"]:not(:checked) + label:hover { - border-color: var(--grey-600); - background-color: var(--grey-800); - } - } - } - - - - - - - - - -.product-gallery { - position: relative; - aspect-ratio: 4 / 3; - - .swiper-slide { - width: 100%; - - figure { - - aspect-ratio: 4 / 3; - width: 100%; - height: 100%; - - img { - width: 100%; - height: 100%; - object-fit: contain; + padding-bottom: 0.2em; } } } - // Swiper navigation arrows - .swiper-button-prev, - .swiper-button-next { - color: var(--color-txt); - width: 20px; - height: 20px; - - &:after { - font-size: 20px; - font-weight: bold; - } - - &:hover { - opacity: 0.7; - } - } - - // Swiper pagination dots - .swiper-pagination { - position: relative; - margin-top: calc(var(--spacing) * 0.5); - bottom: 0; - - .swiper-pagination-bullet { - width: 8px; - height: 8px; - background: var(--grey-600); - opacity: 0.5; - transition: opacity 0.3s; - - &:hover { - opacity: 0.7; - } - } - - .swiper-pagination-bullet-active { - background: var(--color-txt); - opacity: 1; - } - } -} - - - .hero { - margin-bottom: calc(var(--spacing) * 1); - padding: calc(var(--spacing) * 0.5) 0; - border-top: var(--border-light); - border-bottom: var(--border-light); - - .p__baseline-big { - margin: 0; - text-align: left; - } - } - - .add-to-cart { - margin: 0; - border-bottom: var(--border-light); - padding: calc(var(--spacing) * 0.5) 0; - } - - .product-options { - border-bottom: var(--border-light); - padding: calc(var(--spacing) * 0.25) 0; - } - - - -@media #{$small} { - .store__nav a { - padding-top: 0; - font-size: var(--fs-small); - } - - .section__product { + @media #{$small} { display: flex; flex-direction: column; margin-bottom: 10vh; @@ -207,6 +59,7 @@ margin-top: calc(var(--spacing) * 0.5); order: 1; } + figure { order: 2; margin-bottom: calc(var(--spacing) * 1); @@ -226,25 +79,25 @@ order: 5; } - .product-gallery{ + .product-gallery { width: 100vw; position: relative; - left: calc(var(--padding-body)*-1); + left: calc(var(--padding-body) * -1); .swiper-button-prev, - .swiper-button-next{ display: none; } + .swiper-button-next { + display: none; + } } } -} -@media #{$small-up} { - - - .section__product{ - display: grid; - grid-template-columns: 1fr 1fr; - gap: calc(var(--padding-body)*2); - margin-bottom: calc(var(--spacing)*3); + @media #{$small-up} { + .product-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: calc(var(--padding-body) * 2); + margin-bottom: calc(var(--spacing) * 3); + } .details { margin-bottom: calc(var(--spacing) * 2); @@ -255,17 +108,140 @@ border-top: var(--border-light); } - .col-left{ + .col-left { min-height: 100%; - padding-bottom: 40px; //dots - + padding-bottom: 40px; display: flex; flex-direction: column; } - - } - - .product-gallery .swiper-slide figure{ - width: calc(100% - 60px); } } + +.product-options { + border-bottom: var(--border-light); + padding: calc(var(--spacing) * 0.25) 0; +} + +.product-options__list { + list-style: none; + display: flex; + gap: 2ch; + + li { + position: relative; + + input[type="radio"] { + position: fixed; + opacity: 0; + pointer-events: none; + + &:checked + label { + border-color: var(--color-txt); + } + + &:not(:checked) + label:hover { + border-color: var(--grey-600); + background-color: var(--grey-800); + } + } + + label { + font-family: var(--title); + font-size: var(--fs-normal); + height: 4ch; + width: 4ch; + border-radius: 50%; + border: var(--border); + border-color: transparent; + display: flex; + align-items: center; + justify-content: center; + padding-top: 0px; + cursor: pointer; + } + } +} + +.product-gallery { + position: relative; + aspect-ratio: 4 / 3; + + .swiper-slide { + width: 100%; + + figure { + aspect-ratio: 4 / 3; + width: 100%; + height: 100%; + + img { + width: 100%; + height: 100%; + object-fit: contain; + } + } + + @media #{$small-up} { + figure { + width: calc(100% - 60px); + } + } + } + + .swiper-button-prev, + .swiper-button-next { + color: var(--color-txt); + width: 20px; + height: 20px; + + &:after { + font-size: 20px; + font-weight: bold; + } + + &:hover { + opacity: 0.7; + } + } + + .swiper-pagination { + position: relative; + margin-top: calc(var(--spacing) * 0.5); + bottom: 0; + + .swiper-pagination-bullet { + width: 8px; + height: 8px; + background: var(--grey-600); + opacity: 0.5; + transition: opacity 0.3s; + + &:hover { + opacity: 0.7; + } + + &.swiper-pagination-bullet-active { + background: var(--color-txt); + opacity: 1; + } + } + } +} + +.hero { + margin-bottom: calc(var(--spacing) * 1); + padding: calc(var(--spacing) * 0.5) 0; + border-top: var(--border-light); + border-bottom: var(--border-light); + + .p__baseline-big { + margin: 0; + text-align: left; + } +} + +.add-to-cart { + margin: 0; + border-bottom: var(--border-light); + padding: calc(var(--spacing) * 0.5) 0; +} diff --git a/assets/css/template/shop/_thanks.scss b/assets/css/template/shop/_thanks.scss index 0453c62..f05bc1a 100644 --- a/assets/css/template/shop/_thanks.scss +++ b/assets/css/template/shop/_thanks.scss @@ -5,29 +5,30 @@ align-items: center; justify-content: center; padding: calc(var(--spacing) * 4) var(--spacing); - } - .thanks-content { - text-align: center; - max-width: 600px; + .thanks-content { + text-align: center; + max-width: 600px; + display: flex; + flex-direction: column; + align-items: center; - h1 { - font-size: var(--fs-x-big); - margin-bottom: calc(var(--spacing) * 2); - } - - .thanks-message { - font-size: var(--fs-big); - margin-bottom: calc(var(--spacing) * 3); - line-height: 1.6; - - p { - margin-bottom: var(--spacing); + h1 { + margin-bottom: calc(var(--spacing) * 2); } - } - .thanks-actions { - margin-top: calc(var(--spacing) * 3); + .thanks-message { + font-size: var(--fs-medium); + line-height: 1.1; + + p { + margin-bottom: var(--spacing); + } + } + + .thanks-actions { + width: max-content; + } } } diff --git a/assets/js/cart-drawer.js b/assets/js/cart-drawer.js new file mode 100644 index 0000000..439c3d1 --- /dev/null +++ b/assets/js/cart-drawer.js @@ -0,0 +1,290 @@ +/** + * Cart Drawer Component + * Manages the cart sidebar with add/remove/update functionality + */ +(function() { + const drawer = document.getElementById('cart-drawer'); + const emptyState = document.querySelector('[data-cart-empty]'); + const itemsContainer = document.querySelector('[data-cart-items]'); + const checkoutBtn = document.querySelector('[data-cart-checkout]'); + const closeButtons = document.querySelectorAll('[data-cart-close]'); + const totalDisplay = document.querySelector('[data-cart-total]'); + const headerCartBtn = document.querySelector('[data-cart-open]'); + const headerCartCount = document.querySelector('[data-cart-count]'); + + // Get translated text + const removeText = drawer.dataset.textRemove || 'Remove'; + + let currentCart = null; + let cartInstance = null; + + // Wait for ShopifyCart to be available + function initCartDrawer() { + if (typeof ShopifyCart === 'undefined') { + setTimeout(initCartDrawer, 100); + return; + } + + cartInstance = new ShopifyCart({ + domain: 'nv7cqv-bu.myshopify.com', + storefrontAccessToken: 'dec3d35a2554384d149c72927d1cfd1b' + }); + + // Initialize event listeners + setupEventListeners(); + + // Load initial cart state + loadCart(); + } + + function setupEventListeners() { + // Close drawer + closeButtons.forEach(btn => { + btn.addEventListener('click', closeDrawer); + }); + + // Open drawer from header button + if (headerCartBtn) { + headerCartBtn.addEventListener('click', openDrawer); + } + + // Checkout button + checkoutBtn.addEventListener('click', () => { + if (currentCart?.checkoutUrl) { + window.location.href = currentCart.checkoutUrl; + } + }); + + // Listen for custom cart update events + document.addEventListener('cart:updated', (e) => { + currentCart = e.detail.cart; + renderCart(); + openDrawer(); + }); + } + + async function loadCart() { + if (!cartInstance) return; + + try { + const cart = await cartInstance.getCart(); + currentCart = cart; + renderCart(); + } catch (error) { + console.error('Error loading cart:', error); + } + } + + function openDrawer() { + drawer.classList.add('is-open'); + document.body.style.overflow = 'hidden'; + } + + function closeDrawer() { + drawer.classList.remove('is-open'); + document.body.style.overflow = ''; + } + + function calculateTotal() { + if (!currentCart || !currentCart.lines) return 0; + + return currentCart.lines.edges.reduce((total, edge) => { + const item = edge.node; + const price = parseFloat(item.merchandise.price.amount); + const quantity = item.quantity; + return total + (price * quantity); + }, 0); + } + + function formatPrice(amount, currency = 'EUR') { + return new Intl.NumberFormat('fr-FR', { + style: 'currency', + currency: currency + }).format(amount); + } + + function updateCartCount() { + if (!currentCart || !currentCart.lines || currentCart.lines.edges.length === 0) { + if (headerCartCount) { + headerCartCount.textContent = ''; + } + return; + } + + // Calculate total quantity + const totalQty = currentCart.lines.edges.reduce((sum, edge) => { + return sum + edge.node.quantity; + }, 0); + + if (headerCartCount) { + headerCartCount.textContent = totalQty > 0 ? totalQty : ''; + } + } + + function renderCart() { + if (!currentCart || !currentCart.lines || currentCart.lines.edges.length === 0) { + emptyState.classList.remove('hidden'); + itemsContainer.classList.add('hidden'); + checkoutBtn.disabled = true; + if (totalDisplay) { + totalDisplay.textContent = '0,00 €'; + } + updateCartCount(); + return; + } + + emptyState.classList.add('hidden'); + itemsContainer.classList.remove('hidden'); + checkoutBtn.disabled = false; + + // Calculate and display total + const total = calculateTotal(); + const currency = currentCart.lines.edges[0]?.node.merchandise.price.currencyCode || 'EUR'; + if (totalDisplay) { + totalDisplay.textContent = formatPrice(total, currency); + } + + // Update header cart count + updateCartCount(); + + // Render cart items + itemsContainer.innerHTML = currentCart.lines.edges.map(edge => { + const item = edge.node; + const merchandise = item.merchandise; + + return ` +
+
+

${merchandise.product.title}

+ ${merchandise.title !== 'Default Title' ? `

${merchandise.title}

` : ''} +

${formatPrice(parseFloat(merchandise.price.amount), merchandise.price.currencyCode)}

+ +
+ + ${item.quantity} + +
+ + +
+
+ `; + }).join(''); + + // Attach event listeners to quantity buttons + attachQuantityListeners(); + } + + function attachQuantityListeners() { + const buttons = itemsContainer.querySelectorAll('[data-action]'); + + buttons.forEach(btn => { + btn.addEventListener('click', async (e) => { + const action = e.target.dataset.action; + const lineId = e.target.dataset.lineId; + + await handleQuantityChange(action, lineId); + }); + }); + } + + async function handleQuantityChange(action, lineId) { + if (!cartInstance || !currentCart) return; + + // Find the line item + const line = currentCart.lines.edges.find(edge => edge.node.id === lineId); + if (!line) return; + + const currentQty = line.node.quantity; + let newQty = currentQty; + + if (action === 'increase') { + newQty = currentQty + 1; + } else if (action === 'decrease') { + newQty = Math.max(0, currentQty - 1); + } else if (action === 'remove') { + newQty = 0; + } + + // Update cart via API + try { + itemsContainer.classList.add('is-loading'); + + const query = ` + mutation cartLinesUpdate($cartId: ID!, $lines: [CartLineUpdateInput!]!) { + cartLinesUpdate(cartId: $cartId, lines: $lines) { + cart { + id + checkoutUrl + lines(first: 10) { + edges { + node { + id + quantity + merchandise { + ... on ProductVariant { + id + title + price { + amount + currencyCode + } + product { + title + } + } + } + } + } + } + } + userErrors { + field + message + } + } + } + `; + + const data = await cartInstance.query(query, { + cartId: currentCart.id, + lines: [{ + id: lineId, + quantity: newQty + }] + }); + + if (data.cartLinesUpdate.userErrors.length > 0) { + throw new Error(data.cartLinesUpdate.userErrors[0].message); + } + + currentCart = data.cartLinesUpdate.cart; + renderCart(); + + } catch (error) { + console.error('Error updating cart:', error); + alert('Erreur lors de la mise à jour du panier'); + } finally { + itemsContainer.classList.remove('is-loading'); + } + } + + // Public API + window.CartDrawer = { + open: openDrawer, + close: closeDrawer, + updateCart: (cart) => { + currentCart = cart; + renderCart(); + } + }; + + // Initialize when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initCartDrawer); + } else { + initCartDrawer(); + } +})(); diff --git a/assets/js/product-add-to-cart.js b/assets/js/product-add-to-cart.js new file mode 100644 index 0000000..1e421d7 --- /dev/null +++ b/assets/js/product-add-to-cart.js @@ -0,0 +1,70 @@ +(function() { + const cart = new ShopifyCart({ + domain: 'nv7cqv-bu.myshopify.com', + storefrontAccessToken: 'dec3d35a2554384d149c72927d1cfd1b' + }); + + const addToCartBtn = document.querySelector('[data-shopify-add-to-cart]'); + + if (!addToCartBtn) { + return; + } + + const buttonTextDiv = addToCartBtn.querySelector('.txt[data-button-text]'); + + if (!buttonTextDiv) { + console.error('Button text div not found'); + return; + } + + const texts = { + add: addToCartBtn.dataset.textAdd || 'Add to cart', + adding: addToCartBtn.dataset.textAdding || 'Adding...', + added: addToCartBtn.dataset.textAdded || 'Added! ✓', + error: addToCartBtn.dataset.textError || 'Error - Try again' + }; + + addToCartBtn.addEventListener('click', async function(e) { + e.preventDefault(); + + const variantId = this.dataset.variantId; + + if (!variantId) { + console.error('No variant ID found'); + return; + } + + addToCartBtn.disabled = true; + const originalText = buttonTextDiv.textContent; + buttonTextDiv.textContent = texts.adding; + + try { + const cartResult = await cart.addToCart(variantId, 1); + + buttonTextDiv.textContent = texts.added; + addToCartBtn.classList.add('success'); + + document.dispatchEvent(new CustomEvent('cart:updated', { + detail: { cart: cartResult } + })); + + setTimeout(() => { + addToCartBtn.disabled = false; + buttonTextDiv.textContent = originalText; + addToCartBtn.classList.remove('success'); + }, 1500); + + } catch (error) { + console.error('Error adding to cart:', error); + + buttonTextDiv.textContent = texts.error; + addToCartBtn.classList.add('error'); + + setTimeout(() => { + addToCartBtn.disabled = false; + buttonTextDiv.textContent = originalText; + addToCartBtn.classList.remove('error'); + }, 2000); + } + }); +})(); diff --git a/assets/js/product-loader.js b/assets/js/product-loader.js new file mode 100644 index 0000000..58293ce --- /dev/null +++ b/assets/js/product-loader.js @@ -0,0 +1,306 @@ +(async function () { + const container = document.querySelector("[data-product-loader]"); + if (!container) return; + + const handle = container.dataset.shopifyHandle; + const language = container.dataset.language || "fr"; + const isEnglish = language === "en"; + const loadingState = container.querySelector(".product-loading"); + const contentState = container.querySelector(".product-content"); + const errorState = container.querySelector(".product-error"); + + try { + const cart = new ShopifyCart({ + domain: "nv7cqv-bu.myshopify.com", + storefrontAccessToken: "dec3d35a2554384d149c72927d1cfd1b", + }); + + const product = await cart.getProductByHandle(handle); + + if (!product) { + throw new Error("Product not found"); + } + + renderProduct(product, isEnglish); + updateMetaTags(product, isEnglish); + + loadingState.style.display = "none"; + contentState.removeAttribute("style"); + + setTimeout(() => { + if (typeof Swiper !== "undefined" && product.images.edges.length > 0) { + new Swiper(".product-gallery", { + loop: product.images.edges.length > 1, + navigation: { + nextEl: ".swiper-button-next", + prevEl: ".swiper-button-prev", + }, + pagination: { + el: ".swiper-pagination", + clickable: true, + }, + keyboard: { + enabled: true, + }, + }); + } + }, 100); + } catch (error) { + console.error("Error loading product:", error); + loadingState.style.display = "none"; + errorState.style.display = "block"; + } + + function renderProduct(product, isEnglish) { + renderTitle(product, isEnglish); + renderPrice(product); + renderDetails(product, isEnglish); + renderImages(product, isEnglish); + renderOptions(product); + setupAddToCart(product); + } + + function renderTitle(product, isEnglish) { + const titleEl = document.querySelector("[data-product-title]"); + if (titleEl) { + const title = + isEnglish && product.titleEn?.value + ? product.titleEn.value + : product.title; + titleEl.textContent = title; + } + } + + function renderPrice(product) { + const priceEl = document.querySelector("[data-product-price]"); + if (priceEl) { + const price = parseFloat(product.priceRange.minVariantPrice.amount); + priceEl.textContent = price.toFixed(2) + "€"; + } + } + + function renderDetails(product, isEnglish) { + const detailsEl = document.querySelector("[data-product-details]"); + if (detailsEl) { + const description = + isEnglish && product.descriptionEn?.value + ? product.descriptionEn.value.replaceAll("\n", "
") + : product.descriptionHtml || ""; + detailsEl.innerHTML = description; + } + } + + function renderImages(product, isEnglish) { + const imagesContainer = document.querySelector("[data-product-images]"); + + if (imagesContainer && product.images.edges.length > 0) { + const productTitle = + isEnglish && product.titleEn?.value + ? product.titleEn.value + : product.title; + + imagesContainer.innerHTML = product.images.edges + .map((edge) => { + const img = edge.node; + return ` +
+
+ ${img.altText || productTitle} +
+
+ `; + }) + .join(""); + } + } + + function renderOptions(product) { + if (product.variants.edges.length <= 1) return; + + const firstVariant = product.variants.edges[0].node; + if ( + !firstVariant.selectedOptions || + firstVariant.selectedOptions.length === 0 + ) + return; + + const mainOption = firstVariant.selectedOptions[0]; + const optionValues = new Set(); + + product.variants.edges.forEach((edge) => { + const variant = edge.node; + if (variant.selectedOptions && variant.selectedOptions[0]) { + optionValues.add(variant.selectedOptions[0].value); + } + }); + + if (optionValues.size <= 1) return; + + const optionsContainer = document.querySelector("[data-product-options]"); + const optionsList = document.querySelector("[data-product-options-list]"); + + if (!optionsContainer || !optionsList) return; + + const optionName = mainOption.name; + const optionSlug = optionName.toLowerCase().replace(/\s+/g, "-"); + + optionsList.innerHTML = Array.from(optionValues) + .map((value) => { + const uniqueId = `${optionSlug}-${value + .toLowerCase() + .replace(/\s+/g, "-")}`; + const variant = product.variants.edges.find( + (e) => + e.node.selectedOptions && e.node.selectedOptions[0]?.value === value + )?.node; + const isAvailable = variant?.availableForSale || false; + + return ` +
  • + + +
  • + `; + }) + .join(""); + + optionsContainer.style.display = "block"; + + const radios = optionsList.querySelectorAll('input[type="radio"]'); + const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]"); + const buttonText = addToCartBtn?.querySelector("[data-button-text]"); + + radios.forEach((radio) => { + radio.addEventListener("change", function () { + const variantId = this.dataset.variantId; + + if (addToCartBtn) { + addToCartBtn.dataset.variantId = variantId; + addToCartBtn.removeAttribute("disabled"); + } + + if (buttonText) { + buttonText.textContent = + addToCartBtn.dataset.defaultText || "Ajouter au panier"; + } + + const allLi = optionsList.querySelectorAll("li"); + allLi.forEach((li) => li.classList.remove("is-selected")); + this.closest("li").classList.add("is-selected"); + }); + }); + } + + function setupAddToCart(product) { + const addToCartBtn = document.querySelector("[data-shopify-add-to-cart]"); + if (!addToCartBtn) return; + + const productId = product.id.replace("gid://shopify/Product/", ""); + addToCartBtn.dataset.productId = productId; + + const hasMultipleVariants = product.variants.edges.length > 1; + const firstVariant = product.variants.edges[0]?.node; + const hasOptions = + firstVariant?.selectedOptions && firstVariant.selectedOptions.length > 0; + + const uniqueOptions = new Set(); + product.variants.edges.forEach((edge) => { + if (edge.node.selectedOptions && edge.node.selectedOptions[0]) { + uniqueOptions.add(edge.node.selectedOptions[0].value); + } + }); + const hasMultipleOptions = uniqueOptions.size > 1; + + if (hasMultipleVariants && hasOptions && hasMultipleOptions) { + addToCartBtn.setAttribute("disabled", "disabled"); + const buttonText = addToCartBtn.querySelector("[data-button-text]"); + if (buttonText) { + buttonText.textContent = + addToCartBtn.dataset.textChooseOption || "Choisissez une option"; + } + } else { + const firstAvailableVariant = product.variants.edges.find( + (e) => e.node.availableForSale + ); + if (firstAvailableVariant) { + const variantId = firstAvailableVariant.node.id.replace( + "gid://shopify/ProductVariant/", + "" + ); + addToCartBtn.dataset.variantId = variantId; + } + } + } + + function updateMetaTags(product, isEnglish) { + // Update title and description + const title = + isEnglish && product.titleEn?.value + ? product.titleEn.value + : product.title; + const description = + isEnglish && product.descriptionEn?.value + ? product.descriptionEn.value + : product.description; + + // Update Open Graph title + const ogTitle = document.getElementById("og-title"); + if (ogTitle) { + ogTitle.setAttribute("content", title); + } + + // Update Open Graph description + const ogDescription = document.getElementById("og-description"); + if (ogDescription && description) { + const excerpt = description.substring(0, 160); + ogDescription.setAttribute("content", excerpt); + } + + // Update Open Graph image + const ogImage = document.getElementById("og-image"); + if (ogImage && product.images.edges.length > 0) { + ogImage.setAttribute("content", product.images.edges[0].node.url); + } + + // Update product price + const ogPrice = document.getElementById("og-price"); + if (ogPrice) { + const price = parseFloat( + product.priceRange.minVariantPrice.amount + ).toFixed(2); + ogPrice.setAttribute("content", price); + } + + // Update availability + const ogAvailability = document.getElementById("og-availability"); + if (ogAvailability) { + const availability = product.availableForSale + ? "in stock" + : "out of stock"; + ogAvailability.setAttribute("content", availability); + } + + // Update page title + document.title = `${title} | Index.ngo`; + + // Update meta description + let metaDescription = document.querySelector('meta[name="description"]'); + if (metaDescription && description) { + const excerpt = description.substring(0, 160); + metaDescription.setAttribute("content", excerpt); + } + } +})(); diff --git a/assets/js/products-list-loader.js b/assets/js/products-list-loader.js new file mode 100644 index 0000000..be32f4a --- /dev/null +++ b/assets/js/products-list-loader.js @@ -0,0 +1,47 @@ +(async function() { + const container = document.querySelector('[data-products-loader]'); + if (!container) return; + + const language = (container.dataset.language || 'FR').toLowerCase(); + const isEnglish = language === 'en'; + + try { + const cart = new ShopifyCart({ + domain: 'nv7cqv-bu.myshopify.com', + storefrontAccessToken: 'dec3d35a2554384d149c72927d1cfd1b' + }); + + const productsData = await cart.getAllProducts(); + const products = productsData.edges; + + const productsHtml = products.map(edge => { + const product = edge.node; + const image = product.images.edges[0]?.node; + const price = parseFloat(product.priceRange.minVariantPrice.amount).toFixed(2); + const slug = product.handle; + const productUrl = isEnglish ? `/en/${slug}` : `/${slug}`; + const productTitle = isEnglish && product.titleEn?.value + ? product.titleEn.value + : product.title; + + return ` +
    +
    + ${image ? `${image.altText || productTitle}` : ''} +
    +

    + ${productTitle} +

    +

    ${price}€

    + +
    + `; + }).join(''); + + container.innerHTML = productsHtml; + + } catch (error) { + console.error('Error loading products:', error); + container.innerHTML = '

    Erreur lors du chargement des produits

    '; + } +})(); diff --git a/assets/js/shopify-cart.js b/assets/js/shopify-cart.js new file mode 100644 index 0000000..396ae38 --- /dev/null +++ b/assets/js/shopify-cart.js @@ -0,0 +1,395 @@ +/** + * Shopify Storefront API Client + * Custom implementation using Cart API (2026-01) + */ +class ShopifyCart { + constructor(config) { + this.domain = config.domain; + this.storefrontAccessToken = config.storefrontAccessToken; + this.apiVersion = '2026-01'; + this.endpoint = `https://${this.domain}/api/${this.apiVersion}/graphql.json`; + this.cartId = null; + this.cartItems = []; + + // Load existing cart from localStorage + this.loadCart(); + } + + /** + * Make GraphQL request to Shopify Storefront API + */ + async query(query, variables = {}) { + const response = await fetch(this.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Shopify-Storefront-Access-Token': this.storefrontAccessToken, + }, + body: JSON.stringify({ query, variables }), + }); + + const result = await response.json(); + + if (result.errors) { + console.error('Shopify API Error:', result.errors); + throw new Error(result.errors[0].message); + } + + return result.data; + } + + /** + * Get product information by ID + */ + async getProduct(productId) { + const query = ` + query getProduct($id: ID!) { + product(id: $id) { + id + title + description + availableForSale + variants(first: 10) { + edges { + node { + id + title + price { + amount + currencyCode + } + availableForSale + } + } + } + } + } + `; + + const data = await this.query(query, { + id: `gid://shopify/Product/${productId}` + }); + + return data.product; + } + + /** + * Get product by handle + * @param {string} handle - Product handle (slug) + */ + async getProductByHandle(handle) { + const query = ` + query getProductByHandle($handle: String!) { + product(handle: $handle) { + id + handle + title + description + descriptionHtml + availableForSale + tags + titleEn: metafield(namespace: "custom", key: "title_en") { + value + } + descriptionEn: metafield(namespace: "custom", key: "description_en") { + value + } + priceRange { + minVariantPrice { + amount + currencyCode + } + } + images(first: 10) { + edges { + node { + id + url + altText + width + height + } + } + } + variants(first: 20) { + edges { + node { + id + title + sku + availableForSale + price { + amount + currencyCode + } + selectedOptions { + name + value + } + } + } + } + } + } + `; + + const data = await this.query(query, { handle }); + return data.product || null; + } + + /** + * Get all products for listing page + * @param {number} first - Number of products to fetch + */ + async getAllProducts(first = 20) { + const query = ` + query getAllProducts($first: Int!) { + products(first: $first, sortKey: TITLE) { + edges { + node { + id + handle + title + description + availableForSale + titleEn: metafield(namespace: "custom", key: "title_en") { + value + } + priceRange { + minVariantPrice { + amount + currencyCode + } + } + images(first: 1) { + edges { + node { + id + url + altText + } + } + } + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + `; + + const data = await this.query(query, { first }); + return data.products; + } + + /** + * Create a new cart + */ + async createCart(lines = []) { + const query = ` + mutation cartCreate($input: CartInput!) { + cartCreate(input: $input) { + cart { + id + checkoutUrl + lines(first: 10) { + edges { + node { + id + quantity + merchandise { + ... on ProductVariant { + id + title + price { + amount + currencyCode + } + product { + title + } + } + } + } + } + } + } + userErrors { + field + message + } + } + } + `; + + const data = await this.query(query, { + input: { lines } + }); + + if (data.cartCreate.userErrors.length > 0) { + throw new Error(data.cartCreate.userErrors[0].message); + } + + this.cartId = data.cartCreate.cart.id; + this.saveCart(); + + return data.cartCreate.cart; + } + + /** + * Add item to cart + */ + async addToCart(variantId, quantity = 1) { + const lines = [{ + merchandiseId: `gid://shopify/ProductVariant/${variantId}`, + quantity: quantity + }]; + + let cart; + + if (this.cartId) { + // Add to existing cart + const query = ` + mutation cartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) { + cartLinesAdd(cartId: $cartId, lines: $lines) { + cart { + id + checkoutUrl + lines(first: 10) { + edges { + node { + id + quantity + merchandise { + ... on ProductVariant { + id + title + price { + amount + currencyCode + } + product { + title + } + } + } + } + } + } + } + userErrors { + field + message + } + } + } + `; + + const data = await this.query(query, { + cartId: this.cartId, + lines + }); + + if (data.cartLinesAdd.userErrors.length > 0) { + throw new Error(data.cartLinesAdd.userErrors[0].message); + } + + cart = data.cartLinesAdd.cart; + } else { + // Create new cart + cart = await this.createCart(lines); + } + + this.cartItems = cart.lines.edges; + return cart; + } + + /** + * Get existing cart by ID + */ + async getCart() { + if (!this.cartId) { + return null; + } + + const query = ` + query getCart($cartId: ID!) { + cart(id: $cartId) { + id + checkoutUrl + lines(first: 10) { + edges { + node { + id + quantity + merchandise { + ... on ProductVariant { + id + title + price { + amount + currencyCode + } + product { + title + } + } + } + } + } + } + } + } + `; + + try { + const data = await this.query(query, { + cartId: this.cartId + }); + + return data.cart; + } catch (error) { + // Cart might be expired or invalid + console.error('Error fetching cart:', error); + this.clearCart(); + return null; + } + } + + /** + * Get checkout URL to redirect user + */ + getCheckoutUrl(cart) { + return cart?.checkoutUrl || null; + } + + /** + * Save cart ID to localStorage + */ + saveCart() { + if (this.cartId) { + localStorage.setItem('shopify_cart_id', this.cartId); + } + } + + /** + * Load cart ID from localStorage + */ + loadCart() { + this.cartId = localStorage.getItem('shopify_cart_id'); + } + + /** + * Clear cart + */ + clearCart() { + this.cartId = null; + this.cartItems = []; + localStorage.removeItem('shopify_cart_id'); + } +} + +// Export for use in other scripts +window.ShopifyCart = ShopifyCart; diff --git a/assets/js/snipcart.js b/assets/js/snipcart.js index 0e0dfee..13eef65 100644 --- a/assets/js/snipcart.js +++ b/assets/js/snipcart.js @@ -6,13 +6,13 @@ window.SnipcartSettings = { // Redirection après paiement réussi document.addEventListener('snipcart.ready', function() { - Snipcart.execute('bind', 'order.completed', function(order) { + Snipcart.events.on('cart.confirmed', function(cartState) { // Détecter la langue actuelle depuis l'URL const currentPath = window.location.pathname; const langMatch = currentPath.match(/^\/([a-z]{2})(\/|$)/); const langPrefix = langMatch ? '/' + langMatch[1] : ''; - window.location.href = langPrefix + '/thanks?order=' + order.token; + window.location.href = langPrefix + '/thanks?order=' + cartState.token; }); }); diff --git a/assets/snipcart-archive/README.md b/assets/snipcart-archive/README.md new file mode 100644 index 0000000..3b1c388 --- /dev/null +++ b/assets/snipcart-archive/README.md @@ -0,0 +1,164 @@ +# Archive Snipcart + +Cette archive contient tous les fichiers et le code nécessaires pour réactiver l'intégration Snipcart si besoin. + +## Date d'archivage +13 janvier 2026 + +## Raison de l'archivage +Remplacement par l'intégration Shopify Buy Button + +--- + +## Fichiers archivés + +### Fichiers JavaScript +- `snipcart.js` - Loader Snipcart avec configuration de la clé API +- `product-size.js` - Gestion des options produit (tailles, etc.) pour Snipcart + +### Fichiers SCSS +- `_snipcart.scss` - Styles pour le modal Snipcart + +--- + +## Comment restaurer l'intégration Snipcart + +### 1. Restaurer les fichiers JavaScript + +#### Fichier: `assets/js/snipcart.js` +Copier le fichier depuis l'archive: +```bash +cp assets/snipcart-archive/snipcart.js assets/js/ +``` + +#### Fichier: `assets/js/product-size.js` +Copier le fichier depuis l'archive: +```bash +cp assets/snipcart-archive/product-size.js assets/js/ +``` + +### 2. Restaurer les styles SCSS + +#### Fichier: `assets/css/template/shop/_snipcart.scss` +Copier le fichier depuis l'archive: +```bash +cp assets/snipcart-archive/_snipcart.scss assets/css/template/shop/ +``` + +Puis décommenter l'import dans `assets/css/style.scss`: +```scss +// Décommenter cette ligne: +@import 'template/shop/snipcart'; +``` + +### 3. Restaurer le template produit + +#### Fichier: `site/templates/product.php` + +1. **Restaurer le bouton "Ajouter au panier" avec les attributs Snipcart** (ligne ~40-78): + - Décommenter tous les attributs `data-item-*` du bouton + - Ajouter la classe `snipcart-add-item` au bouton + +2. **Restaurer les scripts dans le footer** (ligne 114): + ```php + ['assets/js/product-size.js', 'assets/js/snipcart.js', 'assets/js/product-gallery.js']]) ?> + ``` + +### 4. Restaurer les routes et webhooks + +#### Fichier: `site/config/config.php` + +Décommenter les routes Snipcart (lignes 39-147): + +1. **Route de validation produit** (`validate.json`): + - Permet à Snipcart de valider les prix et stock + - Route: `(:any)/validate.json` + +2. **Webhook Snipcart**: + - Gère les événements de commande (décrémente le stock) + - Route: `snipcart-webhook` + +### 5. Configuration Snipcart + +#### Clé API publique +La clé API publique Snipcart est dans `snipcart.js`: +```javascript +publicApiKey: 'NGU4ODQ3MjAtY2MzMC00MWEyLWI2YTMtNjBmNGYzMTBlOTZkNjM4OTY1NDY4OTE5MTQyMTI3' +``` + +#### Configuration du webhook +Pour que le webhook fonctionne, il faut: +1. Configurer l'URL du webhook dans le dashboard Snipcart +2. URL: `https://votre-domaine.com/snipcart-webhook` +3. Événements à écouter: `order.completed` + +### 6. Vérifications après restauration + +- [ ] Les fichiers JS sont présents dans `assets/js/` +- [ ] Le fichier SCSS est présent et importé +- [ ] Les boutons "Ajouter au panier" ont la classe `snipcart-add-item` +- [ ] Les attributs `data-item-*` sont présents sur les boutons +- [ ] Les routes sont décommentées dans `config.php` +- [ ] Le CSS de Snipcart est compilé +- [ ] Le webhook est configuré dans le dashboard Snipcart +- [ ] Les traductions sont restaurées dans `site/languages/en.php` et `fr.php` + +--- + +## Fonctionnalités Snipcart implémentées + +### Gestion des produits +- Affichage du prix +- Options de produit (tailles, couleurs, etc.) +- Validation des options obligatoires +- Images produit + +### Gestion du panier +- Ajout au panier +- Gestion du stock +- Calcul des frais de port (basé sur poids/dimensions) +- Validation des prix côté serveur + +### Gestion des commandes +- Webhook pour décrémenter le stock automatiquement +- Redirection vers page de remerciement après paiement +- Token de commande dans l'URL + +### Multi-langue +- Support FR/EN +- Redirection post-paiement avec détection de la langue + +--- + +## Dépendances externes + +### CDN Snipcart +Snipcart est chargé depuis le CDN officiel: +- Version: 3.0 +- JS: `https://cdn.snipcart.com/themes/v3.0/default/snipcart.js` +- CSS: `https://cdn.snipcart.com/themes/v3.0/default/snipcart.css` + +### Stratégie de chargement +- `loadStrategy: 'on-user-interaction'` +- Chargement différé pour optimiser les performances +- Timeout: 2750ms + +--- + +## Notes importantes + +1. **Sécurité**: Le webhook devrait valider la signature Snipcart en production (voir commentaire dans `config.php`) + +2. **Stock**: Le système décrémente automatiquement le stock via le webhook `order.completed` + +3. **Validation**: Chaque produit expose une route `validate.json` pour que Snipcart puisse vérifier les prix + +4. **Multi-langue**: La redirection post-paiement détecte automatiquement la langue depuis l'URL + +--- + +## Support + +Pour toute question sur Snipcart: +- Documentation: https://docs.snipcart.com/ +- Support: https://snipcart.com/support diff --git a/assets/css/template/shop/_snipcart.scss b/assets/snipcart-archive/_snipcart.scss similarity index 100% rename from assets/css/template/shop/_snipcart.scss rename to assets/snipcart-archive/_snipcart.scss diff --git a/assets/snipcart-archive/product-size.js b/assets/snipcart-archive/product-size.js new file mode 100644 index 0000000..399ffce --- /dev/null +++ b/assets/snipcart-archive/product-size.js @@ -0,0 +1,66 @@ +/** + * Gestion de la sélection des options produit + * Met à jour les attributs Snipcart et gère les classes CSS + */ + +(function() { + 'use strict'; + + /** + * Initialise la gestion des options + */ + function initOptionSelector() { + const optionsContainer = document.querySelector('.product-options'); + const addToCartButton = document.querySelector('.snipcart-add-item'); + + if (!addToCartButton) { + return; + } + + // Si pas d'options, le bouton est déjà actif + if (!optionsContainer) { + return; + } + + const radios = optionsContainer.querySelectorAll('input[type="radio"]'); + + // Réinitialiser toutes les options (important pour le cache navigateur) + radios.forEach(radio => { + radio.checked = false; + }); + + // Retirer la classe is-selected de tous les li + const allLi = optionsContainer.querySelectorAll('li'); + allLi.forEach(li => li.classList.remove('is-selected')); + + // S'assurer que le bouton est désactivé au départ + addToCartButton.setAttribute('disabled', 'disabled'); + + // Écouter les changements de sélection + radios.forEach(radio => { + radio.addEventListener('change', function() { + // Mettre à jour l'attribut Snipcart + addToCartButton.setAttribute('data-item-custom1-value', this.value); + + // Activer le bouton + addToCartButton.removeAttribute('disabled'); + + // Changer le texte du bouton + const buttonText = addToCartButton.querySelector('.txt'); + if (buttonText) { + buttonText.textContent = buttonText.getAttribute('data-default-text') || 'Ajouter au panier'; + } + + // Gérer la classe is-selected sur les li parents + allLi.forEach(li => li.classList.remove('is-selected')); + this.closest('li').classList.add('is-selected'); + }); + }); + } + + /** + * Initialisation au chargement de la page + */ + document.addEventListener('DOMContentLoaded', initOptionSelector); + +})(); diff --git a/assets/snipcart-archive/snipcart.js b/assets/snipcart-archive/snipcart.js new file mode 100644 index 0000000..13eef65 --- /dev/null +++ b/assets/snipcart-archive/snipcart.js @@ -0,0 +1,94 @@ +window.SnipcartSettings = { + publicApiKey: + 'NGU4ODQ3MjAtY2MzMC00MWEyLWI2YTMtNjBmNGYzMTBlOTZkNjM4OTY1NDY4OTE5MTQyMTI3', + loadStrategy: 'on-user-interaction', +}; + +// Redirection après paiement réussi +document.addEventListener('snipcart.ready', function() { + Snipcart.events.on('cart.confirmed', function(cartState) { + // Détecter la langue actuelle depuis l'URL + const currentPath = window.location.pathname; + const langMatch = currentPath.match(/^\/([a-z]{2})(\/|$)/); + const langPrefix = langMatch ? '/' + langMatch[1] : ''; + + window.location.href = langPrefix + '/thanks?order=' + cartState.token; + }); +}); + +(() => { + var c, d; + (d = (c = window.SnipcartSettings).version) != null || (c.version = '3.0'); + var s, S; + (S = (s = window.SnipcartSettings).timeoutDuration) != null || + (s.timeoutDuration = 2750); + var l, p; + (p = (l = window.SnipcartSettings).domain) != null || + (l.domain = 'cdn.snipcart.com'); + var w, u; + (u = (w = window.SnipcartSettings).protocol) != null || + (w.protocol = 'https'); + var f = + window.SnipcartSettings.version.includes('v3.0.0-ci') || + (window.SnipcartSettings.version != '3.0' && + window.SnipcartSettings.version.localeCompare('3.4.0', void 0, { + numeric: !0, + sensitivity: 'base', + }) === -1), + m = ['focus', 'mouseover', 'touchmove', 'scroll', 'keydown']; + window.LoadSnipcart = o; + document.readyState === 'loading' + ? document.addEventListener('DOMContentLoaded', r) + : r(); + function r() { + window.SnipcartSettings.loadStrategy + ? window.SnipcartSettings.loadStrategy === 'on-user-interaction' && + (m.forEach((t) => document.addEventListener(t, o)), + setTimeout(o, window.SnipcartSettings.timeoutDuration)) + : o(); + } + var a = !1; + function o() { + if (a) return; + a = !0; + let t = document.getElementsByTagName('head')[0], + e = document.querySelector('#snipcart'), + i = document.querySelector( + `src[src^="${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}"][src$="snipcart.js"]` + ), + n = document.querySelector( + `link[href^="${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}"][href$="snipcart.css"]` + ); + e || + ((e = document.createElement('div')), + (e.id = 'snipcart'), + e.setAttribute('hidden', 'true'), + document.body.appendChild(e)), + v(e), + i || + ((i = document.createElement('script')), + (i.src = `${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}/themes/v${window.SnipcartSettings.version}/default/snipcart.js`), + (i.async = !0), + t.appendChild(i)), + n || + ((n = document.createElement('link')), + (n.rel = 'stylesheet'), + (n.type = 'text/css'), + (n.href = `${window.SnipcartSettings.protocol}://${window.SnipcartSettings.domain}/themes/v${window.SnipcartSettings.version}/default/snipcart.css`), + t.prepend(n)), + m.forEach((g) => document.removeEventListener(g, o)); + } + function v(t) { + !f || + ((t.dataset.apiKey = window.SnipcartSettings.publicApiKey), + window.SnipcartSettings.addProductBehavior && + (t.dataset.configAddProductBehavior = + window.SnipcartSettings.addProductBehavior), + window.SnipcartSettings.modalStyle && + (t.dataset.configModalStyle = window.SnipcartSettings.modalStyle), + window.SnipcartSettings.currency && + (t.dataset.currency = window.SnipcartSettings.currency), + window.SnipcartSettings.templatesUrl && + (t.dataset.templatesUrl = window.SnipcartSettings.templatesUrl)); + } +})(); diff --git a/content/1_tshirt-index-01/product.en.txt b/content/1_tshirt-index-01/product.en.txt deleted file mode 100644 index 0541df2..0000000 --- a/content/1_tshirt-index-01/product.en.txt +++ /dev/null @@ -1,52 +0,0 @@ -Title: T-shirt Index 01 - ----- - -Price: 35 - ----- - -Description:

    T-shirt de soutien à Index, 100% coton

    - ----- - -Details: - -[ - { - "content": { - "text": "

    100% cotton

    " - }, - "id": "detail1", - "isHidden": false, - "type": "text" - }, - { - "content": { - "text": "

    Lorem ipsum dolor sit amet

    " - }, - "id": "detail2", - "isHidden": false, - "type": "text" - } -] - ----- - -Options: - -- - label: Size - values: XS, S, M, L, XL - ----- - -Snipcartid: tshirt-01 - ----- - -Backgroundcolor: #ffffff - ----- - -Template: product \ No newline at end of file diff --git a/content/1_tshirt-index-01/product.fr.txt b/content/1_tshirt-index-01/product.fr.txt deleted file mode 100644 index 2226ab3..0000000 --- a/content/1_tshirt-index-01/product.fr.txt +++ /dev/null @@ -1,56 +0,0 @@ -Title: T-shirt Index 01 - ----- - -Price: 35 - ----- - -Stock: 10 - ----- - -Description: T-shirt de soutien à Index, 100% coton - ----- - -Details:

    T-shirt en coton organique avec impression sérigraphique.
    Marquage sur la face avant : logo « INDEX » de 10 cm de large.

    Envoi uniquement via Mondial Relay vers la France, la Belgique et la Suisse.

    - ----- - -Hasoptions: true - ----- - -Optionlabel: Taille - ----- - -Optionvalues: XS, S, M, L, XL - ----- - -Options: - -- - label: Taille - values: XS, S, M, L, XL -- - label: Couleur - values: Rouge, Vert - ----- - -Snipcartid: tshirt-01 - ----- - -Backgroundcolor: #ffffff - ----- - -Template: product - ----- - -Uuid: udrrfizhayqixfoo \ No newline at end of file diff --git a/content/1_tshirt-index-01/tshirt-01.png b/content/1_tshirt-index-01/tshirt-01.png deleted file mode 100644 index 99899b8..0000000 Binary files a/content/1_tshirt-index-01/tshirt-01.png and /dev/null differ diff --git a/content/1_tshirt-index-01/tshirt-01.png.fr.txt b/content/1_tshirt-index-01/tshirt-01.png.fr.txt deleted file mode 100644 index c8fd3f3..0000000 --- a/content/1_tshirt-index-01/tshirt-01.png.fr.txt +++ /dev/null @@ -1,9 +0,0 @@ -Sort: 1 - ----- - -Uuid: deupkqq83jvloz0r - ----- - -Template: image \ No newline at end of file diff --git a/content/error/error.en.txt b/content/error/error.en.txt new file mode 100644 index 0000000..e7a7a4d --- /dev/null +++ b/content/error/error.en.txt @@ -0,0 +1,5 @@ +Title: Error + +---- + +Template: default diff --git a/content/error/error.fr.txt b/content/error/error.fr.txt index 1301277..986fe1a 100644 --- a/content/error/error.fr.txt +++ b/content/error/error.fr.txt @@ -1 +1,5 @@ -Uuid: kcrqkszqasludg2h \ No newline at end of file +Title: Erreur + +---- + +Uuid: kcrqkszqasludg2h diff --git a/content/thanks/thanks.en.txt b/content/thanks/thanks.en.txt new file mode 100644 index 0000000..41baccd --- /dev/null +++ b/content/thanks/thanks.en.txt @@ -0,0 +1,13 @@ +Title: Thank you for your order + +---- + +Text: + +Your order has been confirmed! You will receive a confirmation email shortly. + +Thank you for your purchase. + +---- + +Template: thanks \ No newline at end of file diff --git a/content/thanks/thanks.fr.txt b/content/thanks/thanks.fr.txt index 1972914..067eeda 100644 --- a/content/thanks/thanks.fr.txt +++ b/content/thanks/thanks.fr.txt @@ -7,3 +7,7 @@ Text: Votre commande a été confirmée ! Vous allez recevoir un email de confirmation sous peu. Merci pour votre achat. + +---- + +Template: thanks diff --git a/content/thanks/thanks.txt b/content/thanks/thanks.txt index e968ef6..a2fae52 100644 --- a/content/thanks/thanks.txt +++ b/content/thanks/thanks.txt @@ -1,9 +1 @@ -Title: Thank you for your order - ----- - -Text: - -Your order has been confirmed! You will receive a confirmation email shortly. - -Thank you for your purchase. +Title: Thanks \ No newline at end of file diff --git a/create-og-image.html b/create-og-image.html new file mode 100644 index 0000000..5dc314c --- /dev/null +++ b/create-og-image.html @@ -0,0 +1,73 @@ + + + + + Generate OG Image + + + +

    Générer l'image Open Graph

    +

    Cette page génère une image 1200x630px avec le logo Index pour les partages sociaux.

    + +
    + +
    +
    + + + + diff --git a/site/blueprints/pages/product.yml b/site/blueprints/pages/product.yml index 52fde12..978bcde 100644 --- a/site/blueprints/pages/product.yml +++ b/site/blueprints/pages/product.yml @@ -1,134 +1,21 @@ -title: - en: Product - fr: Produit +title: Product icon: cart -tabs: - content: - label: - en: Content - fr: Contenu - columns: - - width: 2/3 - sections: - main: - type: fields - fields: - price: - label: - en: Price (€) - fr: Prix (€) - type: number - min: 0 - step: 0.01 - required: true - translate: false - width: 1/4 - stock: - label: Stock - type: number - min: 0 - default: 0 - help: - en: Edit through french version - fr: Partagé entre les versions FR et EN - translate: false - width: 1/4 - space: - type: gap - width: 2/4 - weight: - label: - en: Weight (g) - fr: Poids (g) - type: number - min: 0 - default: 0 - help: - en: Weight in grams for shipping calculation - fr: Poids en grammes pour le calcul de la livraison - translate: false - width: 1/4 - length: - label: - en: Length (cm) - fr: Longueur (cm) - type: number - min: 0 - default: 0 - help: - en: Package length in centimeters - fr: Longueur du colis en centimètres - translate: false - width: 1/4 - width: - label: - en: Width (cm) - fr: Largeur (cm) - type: number - min: 0 - default: 0 - help: - en: Package width in centimeters - fr: Largeur du colis en centimètres - translate: false - width: 1/4 - height: - label: - en: Height (cm) - fr: Hauteur (cm) - type: number - min: 0 - default: 0 - help: - en: Package height in centimeters - fr: Hauteur du colis en centimètres - translate: false - width: 1/4 - description: - label: Description panier - type: writer - help: Visible dans le panier seulement. - details: - label: - en: Details - fr: Détails - type: writer - hasOptions: - label: - en: Options - fr: Options - type: toggle - default: false - translate: false - width: 1/7 - optionLabel: - label: - en: Option label - fr: Libellé de l'option - type: text - width: 3/7 - when: - hasOptions: true - optionValues: - label: - en: Option values - fr: Valeurs de l'option - type: tags - help: - en: "Comma-separated values (e.g.: XS, S, M, L, XL)" - fr: "Valeurs séparées par des virgules (ex: XS, S, M, L, XL)" - translate: false - when: - hasOptions: true - width: 3/7 +columns: + - width: 1/1 + fields: + info: + type: info + text: + en: "Product data (title, description, images, price) is managed in Shopify Admin. This Kirby page only serves for routing." + fr: "Les données produit (titre, description, images, prix) sont gérées dans Shopify Admin. Cette page Kirby sert uniquement au routing." - - width: 1/3 - sections: - images: - type: files - headline: - en: Product Images - fr: Images du produit - template: image - layout: cards + shopifyHandle: + label: + en: Shopify Handle + fr: Shopify Handle + type: text + help: + en: "Product handle from Shopify (e.g. tshirt-index-01). If empty, uses the page slug." + fr: "Handle du produit Shopify (ex: tshirt-index-01). Si vide, utilise le slug de la page Kirby." + placeholder: tshirt-index-01 diff --git a/site/blueprints/site.yml b/site/blueprints/site.yml index df442f5..13b775f 100644 --- a/site/blueprints/site.yml +++ b/site/blueprints/site.yml @@ -1,15 +1,11 @@ title: Site sections: - pages: - type: pages - headline: - en: Products - fr: Produits - template: product - sortBy: title asc - info: "{{ page.stock }} en stock" - layout: cardlets - image: - query: page.files.first - cover: true + shopify: + type: fields + fields: + shopifyRefreshButton: + type: shopify-refresh + label: + en: Shopify Products + fr: Produits Shopify diff --git a/site/config/config.php b/site/config/config.php index 55d96fb..5023f17 100644 --- a/site/config/config.php +++ b/site/config/config.php @@ -1,10 +1,142 @@ true, 'languages' => true, + 'cache' => [ + 'shopify' => true + ], + + 'routes' => [ + // Sitemap + [ + 'pattern' => 'sitemap.xml', + 'action' => function() { + $sitemap = page('home'); + return new Kirby\Cms\Response( + snippet('sitemap', ['page' => $sitemap], true), + 'application/xml' + ); + } + ], + // English homepage + [ + 'pattern' => 'en', + 'action' => function() { + $home = page('home'); + if ($home) { + site()->visit($home, 'en'); + return $home; + } + return null; + } + ], + // English thanks page + [ + 'pattern' => 'en/thanks', + 'action' => function() { + $thanks = page('thanks'); + if ($thanks) { + site()->visit($thanks, 'en'); + return $thanks; + } + return null; + } + ], + // English error page + [ + 'pattern' => 'en/error', + 'action' => function() { + $error = page('error'); + if ($error) { + site()->visit($error, 'en'); + return $error; + } + return null; + } + ], + // French thanks page + [ + 'pattern' => 'thanks', + 'action' => function() { + return page('thanks'); + } + ], + // French error page + [ + 'pattern' => 'error', + 'action' => function() { + return page('error'); + } + ], + // French products (default) + [ + 'pattern' => '(:any)', + 'action' => function($slug) { + if (in_array($slug, ['home', 'error', 'thanks'])) { + return null; + } + + $products = getShopifyProducts(); + + foreach ($products as $product) { + if ($product['handle'] === $slug) { + $page = Page::factory([ + 'slug' => $product['handle'], + 'template' => 'product', + 'content' => [ + 'title' => $product['title'], + 'shopifyHandle' => $product['handle'], + 'uuid' => $product['id'] + ] + ]); + + site()->visit($page, 'fr'); + return $page; + } + } + + return null; + } + ], + // English products + [ + 'pattern' => 'en/(:any)', + 'action' => function($slug) { + if (in_array($slug, ['home', 'error', 'thanks'])) { + return null; + } + + $products = getShopifyProducts(); + + foreach ($products as $product) { + if ($product['handle'] === $slug) { + $page = Page::factory([ + 'slug' => $product['handle'], + 'template' => 'product', + 'content' => [ + 'title' => $product['title'], + 'shopifyHandle' => $product['handle'], + 'uuid' => $product['id'] + ] + ]); + + site()->visit($page, 'en'); + return $page; + } + } + + return null; + } + ] + ], + 'thumbs' => [ 'quality' => 85, 'format' => 'webp', @@ -35,114 +167,4 @@ return [ ], ], ], - - 'routes' => [ - [ - 'pattern' => '(:any)/validate.json', - 'method' => 'GET', - 'action' => function ($slug) { - $page = page($slug); - - if (!$page || $page->intendedTemplate() !== 'product') { - header('Content-Type: application/json'); - http_response_code(404); - echo json_encode(['error' => 'Product not found']); - return; - } - - // Récupérer le stock actuel - $stock = (int) $page->stock()->value(); - - // Préparer la réponse JSON pour Snipcart - $response = [ - 'id' => $page->slug(), - 'price' => (float) $page->price()->value(), - 'url' => $page->url() . '/validate.json', - 'name' => $page->title()->value(), - 'description' => $page->description()->value(), - 'image' => $page->images()->first() ? $page->images()->first()->url() : '', - 'inventory' => $stock, - 'stock' => $stock - ]; - - // Ajouter les options si disponibles - if ($page->hasOptions()->toBool() && $page->optionValues()->isNotEmpty()) { - $values = $page->optionValues()->split(','); - $trimmedValues = array_map('trim', $values); - $snipcartOptions = implode('|', $trimmedValues); - - $response['customFields'] = [ - [ - 'name' => $page->optionLabel()->value(), - 'options' => $snipcartOptions, - 'required' => true - ] - ]; - } - - header('Content-Type: application/json'); - echo json_encode($response); - } - ], - [ - 'pattern' => 'snipcart-webhook', - 'method' => 'POST', - 'action' => function () { - // Webhook handler pour Snipcart - // Vérifie la signature et décrémente le stock - - $requestBody = file_get_contents('php://input'); - $event = json_decode($requestBody, true); - - // Vérifier la signature Snipcart (à implémenter avec la clé secrète) - // $signature = $_SERVER['HTTP_X_SNIPCART_REQUESTTOKEN'] ?? ''; - - if (!$event || !isset($event['eventName'])) { - return Response::json(['error' => 'Invalid request'], 400); - } - - // Gérer l'événement order.completed - if ($event['eventName'] === 'order.completed') { - $order = $event['content'] ?? null; - - if ($order && isset($order['items'])) { - // Impersonate pour avoir les permissions d'écriture - kirby()->impersonate('kirby'); - - foreach ($order['items'] as $item) { - $productId = $item['id'] ?? null; - $quantity = $item['quantity'] ?? 0; - - if ($productId && $quantity > 0) { - // Trouver le produit par son slug - $products = site()->index()->filterBy('intendedTemplate', 'product'); - - foreach ($products as $product) { - if ($product->slug() === $productId) { - // Décrémenter le stock - $currentStock = (int) $product->stock()->value(); - $newStock = max(0, $currentStock - $quantity); - - // Mettre à jour le stock - try { - $product->update([ - 'stock' => $newStock - ]); - } catch (Exception $e) { - // Log l'erreur mais continue le traitement - error_log('Webhook stock update error: ' . $e->getMessage()); - } - - break; - } - } - } - } - } - } - - return Response::json(['status' => 'success'], 200); - } - ] - ] ]; diff --git a/site/config/shopify.php b/site/config/shopify.php new file mode 100644 index 0000000..cbd0d2e --- /dev/null +++ b/site/config/shopify.php @@ -0,0 +1,82 @@ +cache('shopify'); + $products = $cache->get('products'); + + if ($products === null) { + $products = fetchShopifyProducts(); + $cache->set('products', $products, 60); // Cache 60 minutes + } + + return $products; +} + +/** + * Appel direct à l'API Shopify Storefront + */ +function fetchShopifyProducts(): array +{ + $domain = 'nv7cqv-bu.myshopify.com'; + $token = 'dec3d35a2554384d149c72927d1cfd1b'; + $apiVersion = '2026-01'; + $endpoint = "https://{$domain}/api/{$apiVersion}/graphql.json"; + + $query = ' + query getAllProducts { + products(first: 250, sortKey: TITLE) { + edges { + node { + id + handle + title + } + } + } + } + '; + + $ch = curl_init($endpoint); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Nécessaire pour dev local sur Windows + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json', + 'X-Shopify-Storefront-Access-Token: ' . $token + ]); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ + 'query' => $query + ])); + + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($httpCode !== 200) { + error_log("Shopify API error: HTTP {$httpCode}"); + return []; + } + + $data = json_decode($response, true); + + if (isset($data['errors'])) { + error_log("Shopify API GraphQL errors: " . json_encode($data['errors'])); + return []; + } + + $products = []; + foreach ($data['data']['products']['edges'] as $edge) { + $node = $edge['node']; + $products[] = [ + 'id' => $node['id'], + 'handle' => $node['handle'], + 'title' => $node['title'] + ]; + } + + return $products; +} diff --git a/site/controllers/product.php b/site/controllers/product.php new file mode 100644 index 0000000..bbf1fe7 --- /dev/null +++ b/site/controllers/product.php @@ -0,0 +1,11 @@ +shopifyHandle()->or($page->slug())->value(); + $language = $kirby->language()->code(); + + return [ + 'shopifyHandle' => $shopifyHandle, + 'language' => $language + ]; +}; diff --git a/site/languages/en.php b/site/languages/en.php index 55c6d63..e356f2c 100644 --- a/site/languages/en.php +++ b/site/languages/en.php @@ -13,7 +13,24 @@ return [ 'backToShop' => 'Back to shop', 'supportText' => 'To support us, you can also', 'makeDonation' => 'make a donation', + + // Shop / Cart 'addToCart' => 'Add to cart', + 'cart' => 'Cart', + 'cartEmpty' => 'Your cart is empty', + 'total' => 'Total', + 'checkout' => 'Checkout', + 'remove' => 'Remove', + 'inStock' => 'In stock', + 'outOfStock' => 'Out of stock', + 'addingToCart' => 'Adding...', + 'addedToCart' => 'Added! ✓', + 'errorAddToCart' => 'Error - Try again', + 'closeCart' => 'Close cart', + 'loading' => 'Loading...', + 'productNotFound' => 'Product not found', + 'selectVariant' => 'Select', + 'chooseOption' => 'Choose an option', // Blueprints - Home 'home.title' => 'Home', diff --git a/site/languages/fr.php b/site/languages/fr.php index 64f3f5e..98c263d 100644 --- a/site/languages/fr.php +++ b/site/languages/fr.php @@ -13,7 +13,24 @@ return [ 'backToShop' => 'Retour à la boutique', 'supportText' => 'Pour nous soutenir, vous pouvez aussi', 'makeDonation' => 'faire un don', + + // Shop / Cart 'addToCart' => 'Ajouter au panier', + 'cart' => 'Panier', + 'cartEmpty' => 'Votre panier est vide', + 'total' => 'Total', + 'checkout' => 'Passer commande', + 'remove' => 'Retirer', + 'inStock' => 'En stock', + 'outOfStock' => 'Rupture de stock', + 'addingToCart' => 'Ajout en cours...', + 'addedToCart' => 'Ajouté ! ✓', + 'errorAddToCart' => 'Erreur - Réessayer', + 'closeCart' => 'Fermer le panier', + 'loading' => 'Chargement...', + 'productNotFound' => 'Produit non trouvé', + 'selectVariant' => 'Choisir', + 'chooseOption' => 'Choisissez une option', // Blueprints - Home 'home.title' => 'Accueil', diff --git a/site/plugins/shopify-refresh-button/.editorconfig b/site/plugins/shopify-refresh-button/.editorconfig new file mode 100644 index 0000000..3b762c9 --- /dev/null +++ b/site/plugins/shopify-refresh-button/.editorconfig @@ -0,0 +1,20 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.php] +indent_size = 4 + +[*.md,*.txt] +trim_trailing_whitespace = false +insert_final_newline = false + +[composer.json] +indent_size = 4 diff --git a/site/plugins/shopify-refresh-button/.gitattributes b/site/plugins/shopify-refresh-button/.gitattributes new file mode 100644 index 0000000..033ba13 --- /dev/null +++ b/site/plugins/shopify-refresh-button/.gitattributes @@ -0,0 +1,11 @@ +# Note: You need to uncomment the lines you want to use; the other lines can be deleted + +# Git +# .gitattributes export-ignore +# .gitignore export-ignore + +# Tests +# /.coveralls.yml export-ignore +# /.travis.yml export-ignore +# /phpunit.xml.dist export-ignore +# /tests/ export-ignore diff --git a/site/plugins/shopify-refresh-button/.gitignore b/site/plugins/shopify-refresh-button/.gitignore new file mode 100644 index 0000000..4d81cf5 --- /dev/null +++ b/site/plugins/shopify-refresh-button/.gitignore @@ -0,0 +1,14 @@ +# OS files +.DS_Store + +# npm modules +/node_modules + +# Parcel cache folder +.cache + +# Composer files +/vendor + +# kirbyup temp development entry +/index.dev.mjs diff --git a/site/plugins/shopify-refresh-button/LICENSE.md b/site/plugins/shopify-refresh-button/LICENSE.md new file mode 100755 index 0000000..8e663d7 --- /dev/null +++ b/site/plugins/shopify-refresh-button/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/site/plugins/shopify-refresh-button/README.md b/site/plugins/shopify-refresh-button/README.md new file mode 100755 index 0000000..ad2b202 --- /dev/null +++ b/site/plugins/shopify-refresh-button/README.md @@ -0,0 +1,117 @@ +# Kirby Pluginkit: Example plugin for Kirby + +> Variant "Panel plugin setup" + +This is a boilerplate for a Kirby Panel plugin that can be installed via all three [supported installation methods](https://getkirby.com/docs/guide/plugins/plugin-setup-basic#the-three-plugin-installation-methods). + +You can find a list of Pluginkit variants on the [`master` branch](https://github.com/getkirby/pluginkit/tree/master). + +**** + +## How to use the Pluginkit + +1. Fork this repository +2. Change the plugin name and description in the `composer.json` +3. Change the plugin name in the `index.php` and `src/index.js` +4. Change the license if you don't want to publish under MIT +5. Add your plugin code to the `index.php` and `src/index.js` +6. Update this `README` with instructions for your plugin + +### Install the development and build setup + +We use [kirbyup](https://github.com/johannschopplich/kirbyup) for the development and build setup. + +You can start developing directly. kirbyup will be fetched remotely with your first `npm run` command, which may take a short amount of time. + +### Development + +You can start the dev process with: + +```bash +npm run dev +``` + +This will automatically update the `index.js` and `index.css` of your plugin as soon as you make changes. +Reload the Panel to see your code changes reflected. + +With kirbyup 2.0.0+ and Kirby 3.7.4+ you can alternatively use hot module reloading (HMR): + +```bash +npm run serve +``` + +This will start a development server that updates the page as soon as you make changes. Some updates are instant, like CSS or Vue template changes, others require a reload of the page, which happens automatically. + +> [!NOTE] +> The live reload functionality requires top level await, [which is only supported in modern browsers](https://caniuse.com/mdn-javascript_operators_await_top_level). If you're developing in older browsers, use `npm run dev` and reload the page manually to see changes. + +### Production + +As soon as you are happy with your plugin, you should build the final version with: + +```bash +npm run build +``` + +This will automatically create a minified and optimized version of your `index.js` and `index.css` +which you can ship with your plugin. + +We have a tutorial on how to build your own plugin based on the Pluginkit [in the Kirby documentation](https://getkirby.com/docs/guide/plugins/plugin-setup-basic). + +### Build reproducibility + +While kirbyup will stay backwards compatible, exact build reproducibility may be of importance to you. If so, we recommend to target a specific package version, rather than using npx: + +```json +{ + "scripts": { + "dev": "kirbyup src/index.js --watch", + "build": "kirbyup src/index.js" + }, + "devDependencies": { + "kirbyup": "^3.1.0" + } +} +``` + +What follows is an example README for your plugin. + +**** + +## Installation + +### Download + +Download and copy this repository to `/site/plugins/{{ plugin-name }}`. + +### Git submodule + +```bash +git submodule add https://github.com/{{ your-name }}/{{ plugin-name }}.git site/plugins/{{ plugin-name }} +``` + +### Composer + +```bash +composer require {{ your-name }}/{{ plugin-name }} +``` + +## Setup + +*Additional instructions on how to configure the plugin (e.g. blueprint setup, config options, etc.)* + +## Options + +*Document the options and APIs that this plugin offers* + +## Development + +*Add instructions on how to help working on the plugin (e.g. npm setup, Composer dev dependencies, etc.)* + +## License + +MIT + +## Credits + +- [Your Name](https://github.com/ghost) diff --git a/site/plugins/shopify-refresh-button/SECURITY.md b/site/plugins/shopify-refresh-button/SECURITY.md new file mode 100644 index 0000000..3726336 --- /dev/null +++ b/site/plugins/shopify-refresh-button/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +## Supported Versions + +*Use this section to tell people about which versions of your project are currently being supported with security updates.* + +| Version | Supported | +| ------- | ------------------ | +| 5.1.x | :white_check_mark: | +| 5.0.x | :x: | +| 4.0.x | :white_check_mark: | +| < 4.0 | :x: | + +## Reporting a Vulnerability + +*Use this section to tell people how to report a vulnerability.* + +*Tell them where to go, how often they can expect to get an update on a reported vulnerability, what to expect if the vulnerability is accepted or declined, etc.* diff --git a/site/plugins/shopify-refresh-button/composer.json b/site/plugins/shopify-refresh-button/composer.json new file mode 100755 index 0000000..fa07b14 --- /dev/null +++ b/site/plugins/shopify-refresh-button/composer.json @@ -0,0 +1,21 @@ +{ + "name": "getkirby/pluginkit", + "description": "Kirby Example Plugin", + "license": "MIT", + "type": "kirby-plugin", + "version": "1.0.0", + "authors": [ + { + "name": "Your Name", + "email": "you@example.com" + } + ], + "require": { + "getkirby/composer-installer": "^1.1" + }, + "config": { + "allow-plugins": { + "getkirby/composer-installer": true + } + } +} diff --git a/site/plugins/shopify-refresh-button/composer.lock b/site/plugins/shopify-refresh-button/composer.lock new file mode 100644 index 0000000..a5ae0fa --- /dev/null +++ b/site/plugins/shopify-refresh-button/composer.lock @@ -0,0 +1,66 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "37a8e61308b9b6f49cb9835f477f0c64", + "packages": [ + { + "name": "getkirby/composer-installer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/getkirby/composer-installer.git", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getkirby/composer-installer/zipball/c98ece30bfba45be7ce457e1102d1b169d922f3d", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.8 || ^2.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Kirby\\ComposerInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "Kirby\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Kirby's custom Composer installer for the Kirby CMS and for Kirby plugins", + "homepage": "https://getkirby.com", + "support": { + "issues": "https://github.com/getkirby/composer-installer/issues", + "source": "https://github.com/getkirby/composer-installer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://getkirby.com/buy", + "type": "custom" + } + ], + "time": "2020-12-28T12:54:39+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/site/plugins/shopify-refresh-button/index.js b/site/plugins/shopify-refresh-button/index.js new file mode 100644 index 0000000..677cea5 --- /dev/null +++ b/site/plugins/shopify-refresh-button/index.js @@ -0,0 +1,2 @@ +(function(){"use strict";function l(n,r,t,e,o,a,s,f){var i=typeof n=="function"?n.options:n;return r&&(i.render=r,i.staticRenderFns=t,i._compiled=!0),{exports:n,options:i}}const p={__name:"ShopifyRefreshButton",props:{products:{type:Array,default:()=>[]}},setup(n){const r=n,t=Vue.ref("Synchroniser depuis Shopify"),e=Vue.ref("refresh"),o=Vue.ref("aqua-icon"),a=Vue.ref(!1),s=Vue.ref([]);Vue.onMounted(()=>{s.value=r.products||[]});const f=Vue.computed(()=>{const u=s.value.length;return`${u} produit${u>1?"s":""} Shopify en cache`}),i=Vue.computed(()=>s.value.length===0?"Aucun produit trouvé. Cliquez sur 'Rafraîchir Shopify' pour récupérer les produits.":s.value.map(u=>`• ${u.title}
    `).join(` +`));async function y(){a.value=!0,e.value="loader",o.value="orange-icon",t.value="En cours…";try{const c=await(await fetch("/shopify/refresh-cache.json",{method:"POST",headers:{"Content-Type":"application/json"}})).json();if(c.status==="error")throw new Error(c.message);s.value=c.products||[],t.value=`Terminé - ${c.count} produit${c.count>1?"s":""}`,e.value="check",o.value="green-icon",setTimeout(()=>{t.value="Synchroniser depuis Shopify",e.value="refresh",o.value="aqua-icon",a.value=!1},3e3)}catch(u){console.error(u),t.value="Erreur",e.value="alert",o.value="red-icon",setTimeout(()=>{t.value="Synchroniser depuis Shopify",e.value="refresh",o.value="aqua-icon",a.value=!1},3e3)}}return{__sfc:!0,props:r,text:t,icon:e,theme:o,isProcessing:a,currentProducts:s,infoLabel:f,infoText:i,refreshCache:y}}};var h=function(){var r=this,t=r._self._c,e=r._self._setupProxy;return t("div",[t("k-info-field",{attrs:{label:e.infoLabel,text:e.infoText,theme:"info"}}),t("k-button",{staticStyle:{"margin-top":"1rem"},attrs:{theme:e.theme,variant:"dimmed",icon:e.icon,title:"Synchroniser le cache des produits Shopify",disabled:e.isProcessing},on:{click:function(o){return e.refreshCache()}}},[r._v(" "+r._s(e.text)+" ")])],1)},d=[],v=l(p,h,d);const m=v.exports;window.panel.plugin("index/shopify-refresh-button",{fields:{"shopify-refresh":m}})})(); diff --git a/site/plugins/shopify-refresh-button/index.php b/site/plugins/shopify-refresh-button/index.php new file mode 100755 index 0000000..2fd9cdf --- /dev/null +++ b/site/plugins/shopify-refresh-button/index.php @@ -0,0 +1,46 @@ + [ + 'shopify-refresh' => [ + 'props' => [ + 'products' => function() { + return getShopifyProducts(); + } + ], + ] + ], + 'routes' => [ + [ + 'pattern' => 'shopify/refresh-cache.json', + 'method' => 'POST', + 'action' => function() { + if (!kirby()->user()) { + return [ + 'status' => 'error', + 'message' => 'Unauthorized' + ]; + } + + try { + kirby()->cache('shopify')->flush(); + + $products = fetchShopifyProducts(); + + return [ + 'status' => 'success', + 'message' => 'Cache Shopify rafraîchi avec succès', + 'count' => count($products), + 'products' => $products + ]; + } catch (\Throwable $e) { + return [ + 'status' => 'error', + 'message' => 'Erreur lors du rafraîchissement du cache', + 'details' => $e->getMessage() + ]; + } + } + ] + ] +]); diff --git a/site/plugins/shopify-refresh-button/package.json b/site/plugins/shopify-refresh-button/package.json new file mode 100644 index 0000000..bdbe47f --- /dev/null +++ b/site/plugins/shopify-refresh-button/package.json @@ -0,0 +1,7 @@ +{ + "scripts": { + "dev": "npx -y kirbyup src/index.js --watch", + "serve": "npx -y kirbyup serve src/index.js", + "build": "npx -y kirbyup src/index.js" + } +} diff --git a/site/plugins/shopify-refresh-button/src/components/ShopifyRefreshButton.vue b/site/plugins/shopify-refresh-button/src/components/ShopifyRefreshButton.vue new file mode 100644 index 0000000..83425e4 --- /dev/null +++ b/site/plugins/shopify-refresh-button/src/components/ShopifyRefreshButton.vue @@ -0,0 +1,97 @@ + + + diff --git a/site/plugins/shopify-refresh-button/src/index.js b/site/plugins/shopify-refresh-button/src/index.js new file mode 100755 index 0000000..7fedea6 --- /dev/null +++ b/site/plugins/shopify-refresh-button/src/index.js @@ -0,0 +1,7 @@ +import ShopifyRefreshButton from "./components/ShopifyRefreshButton.vue"; + +window.panel.plugin("index/shopify-refresh-button", { + fields: { + "shopify-refresh": ShopifyRefreshButton + } +}); diff --git a/site/snippets/buy-button--t-shirt.php b/site/snippets/buy-button--t-shirt.php new file mode 100644 index 0000000..489b08d --- /dev/null +++ b/site/snippets/buy-button--t-shirt.php @@ -0,0 +1,20 @@ +
    +
    +

    +
    + + +
    \ No newline at end of file diff --git a/site/snippets/buy-button.php b/site/snippets/buy-button.php new file mode 100644 index 0000000..4f2bc74 --- /dev/null +++ b/site/snippets/buy-button.php @@ -0,0 +1,20 @@ +
    + +
    diff --git a/site/snippets/cart-drawer.php b/site/snippets/cart-drawer.php new file mode 100644 index 0000000..8ba5459 --- /dev/null +++ b/site/snippets/cart-drawer.php @@ -0,0 +1,32 @@ +
    +
    +
    +
    +

    + +
    + +
    +
    +

    +
    + +
    +
    + + +
    +
    \ No newline at end of file diff --git a/site/snippets/footer.php b/site/snippets/footer.php index 6acee68..91cbf60 100644 --- a/site/snippets/footer.php +++ b/site/snippets/footer.php @@ -1,3 +1,5 @@ + +
    - - - - + + + + + + + + + + + diff --git a/site/snippets/header.php b/site/snippets/header.php index ff9bf58..f25287e 100644 --- a/site/snippets/header.php +++ b/site/snippets/header.php @@ -1,19 +1,16 @@ - - - - <?= $title ?? $page->title() ?> | Index.ngo - + + - + diff --git a/site/snippets/seo.php b/site/snippets/seo.php new file mode 100644 index 0000000..2959ace --- /dev/null +++ b/site/snippets/seo.php @@ -0,0 +1,79 @@ +language()->code(); + +// Basic meta +$title = $page->customTitle()->or($page->title())->value(); +$siteName = 'Index.ngo'; +$fullTitle = $title . ' | ' . $siteName; + +// Default descriptions by language +$defaultDescriptionFr = 'Boutique de Index, ONG d\'investigation indépendante'; +$defaultDescriptionEn = 'Index shop, independent investigative NGO'; +$defaultDescription = $lang == 'en' ? $defaultDescriptionEn : $defaultDescriptionFr; + +$description = $page->metaDescription()->or($page->description())->value(); +if ($description) { + $description = excerpt($description, 160); +} else { + $description = $defaultDescription; +} + +$url = $page->url(); +// Use product image if available, otherwise use default OG image +// TODO: Create assets/og-logo.png (1200x630px) with Index logo + description +$image = $page->image() ? $page->image()->url() : url('assets/favicon.png'); +?> + + + + +<?= $fullTitle ?> + + + + + + +languages() as $language): ?> + + + + + + + + + + + + + + +languages() as $language): ?> +code() != $lang): ?> + + + +template() == 'product'): ?> + + + + + + + + + + + + + + + + + diff --git a/site/snippets/sitemap.php b/site/snippets/sitemap.php new file mode 100644 index 0000000..122172d --- /dev/null +++ b/site/snippets/sitemap.php @@ -0,0 +1,54 @@ +' ?> + +index()->filterBy('template', 'in', ['home', 'error', 'thanks']); + +foreach ($pages as $p) { + foreach ($kirby->languages() as $lang) { + $url = $p->url($lang->code()); +?> + + + modified('c', 'date') ?> + monthly + isHomePage() ? '1.0' : '0.8' ?> + languages() as $altLang): ?> + + + +languages() as $lang) { + $url = $lang->code() == 'fr' + ? $site->url() . '/' . $product['handle'] + : $site->url() . '/en/' . $product['handle']; +?> + + + + weekly + 0.9 + languages() as $altLang): ?> + code() == 'fr' + ? $site->url() . '/' . $product['handle'] + : $site->url() . '/en/' . $product['handle']; + ?> + + + + + diff --git a/site/snippets/structured-data-product.php b/site/snippets/structured-data-product.php new file mode 100644 index 0000000..2390bc8 --- /dev/null +++ b/site/snippets/structured-data-product.php @@ -0,0 +1,94 @@ + + + diff --git a/site/templates/home.php b/site/templates/home.php index cce6b62..dcb47e5 100644 --- a/site/templates/home.php +++ b/site/templates/home.php @@ -1,35 +1,24 @@ $site->title(), 'template' => 'store']) ?> -
    -

    - baseline()->or('Bienvenue sur la boutique de soutien à Index') ?> -

    +
    +

    + baseline()->or('Bienvenue sur la boutique de soutien à Index') ?> +

    -
    - children()->listed() as $product): ?> -
    -
    - files()->sort()->first()): ?> - $cover, - 'alt' => $product->title()->html(), - 'preset' => 'product-card', - 'size' => 25, - 'lazy' => true - ]) ?> - -
    -

    title()->html() ?>

    -

    price() ?>€

    - -
    - -
    +
    -

    - - -

    -
    +
    +

    +
    + + + +

    + + +

    +
    diff --git a/site/templates/product.php b/site/templates/product.php index 4fc4969..a750891 100644 --- a/site/templates/product.php +++ b/site/templates/product.php @@ -1,115 +1,52 @@ - $page->title(), 'template' => 'shop']) ?> +shopifyHandle()->or($page->slug()); -
    - +snippet('header', ['title' => $page->title(), 'template' => 'shop']); +?> -
    +
    + + +
    + +
    +

    +
    + +
    -
    + - ['assets/js/product-size.js', 'assets/js/snipcart.js', 'assets/js/product-gallery.js']]) ?> + +
    +
    + + + ['product']]) ?>