Initial commit: Sale packages

This commit is contained in:
Ernad Husremovic 2025-08-29 15:20:49 +02:00
commit 14e3d26998
6469 changed files with 2479670 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

View file

@ -0,0 +1,3 @@
<svg width="24" height="8" viewBox="0 0 24 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3594 0V8H0.359375V0H23.3594ZM18.3594 1V3.5H15.3594V4.5H18.3594V7L21.3594 4L18.3594 1ZM5.35938 1L2.35938 4L5.35938 7V4.5H8.35938V3.5H5.35938V1Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 313 B

View file

@ -0,0 +1,4 @@
<svg width="23" height="8" viewBox="0 0 23 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 0H12V8H21V0Z" fill="#CDCDCD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 0V8H11V0H2Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 225 B

View file

@ -0,0 +1,4 @@
<svg width="24" height="8" viewBox="0 0 24 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.3594 0H15.3594V8H21.3594V0Z" fill="#CDCDCD"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.35938 0V8H13.3594V0H2.35938Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 257 B

View file

@ -0,0 +1,3 @@
<svg width="24" height="8" viewBox="0 0 24 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 0V8H21V0H2ZM14.36 5.56C14.45 5.65 14.5 5.75 14.5 5.87C14.5 5.99 14.45 6.1 14.36 6.18L13.69 6.8C13.6 6.89 13.49 6.93 13.35 6.93C13.21 6.93 13.11 6.89 13.01 6.8L11.56 5.46L10.11 6.8C10.02 6.89 9.91 6.93 9.77 6.93C9.63 6.93 9.53 6.89 9.43 6.8L8.76 6.18C8.67 6.09 8.62 5.99 8.62 5.87C8.62 5.75 8.67 5.64 8.76 5.56L10.21 4.22L8.76 2.88C8.67 2.79 8.62 2.69 8.62 2.57C8.62 2.45 8.67 2.34 8.76 2.26L9.43 1.64C9.52 1.55 9.63 1.51 9.77 1.51C9.91 1.51 10.01 1.55 10.11 1.64L11.56 2.98L13.01 1.64C13.1 1.55 13.21 1.51 13.35 1.51C13.49 1.51 13.59 1.55 13.69 1.64L14.36 2.26C14.45 2.35 14.5 2.45 14.5 2.57C14.5 2.69 14.45 2.8 14.36 2.88L12.91 4.22L14.36 5.56Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 775 B

View file

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_add_to_cart_shirt">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_add_to_cart_watch">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_add_to_cart_pants">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_add_to_cart_item">
<rect width="48" height="60" fill="#ffffff"/>
<rect x="5" y="43" width="27" height="2" fill="#222222"/>
<rect x="36" y="47" width="7" height="7" rx="2" fill="#3aadaa"/>
<rect x="11" y="50" width="15" height="2" fill="#595959"/>
<path d="M1.116-11.744H.5a1.827,1.827,0,0,0,.486,1.192,1.818,1.818,0,0,0,1.152.5v.594h.4v-.594a2.2,2.2,0,0,0,.638-.145,1.559,1.559,0,0,0,.5-.315A1.432,1.432,0,0,0,4-10.983a1.57,1.57,0,0,0,.12-.63A1.207,1.207,0,0,0,4-12.186a1.393,1.393,0,0,0-.308-.4,1.571,1.571,0,0,0-.377-.25,1.975,1.975,0,0,0-.341-.127l-.435-.116V-14.86a.964.964,0,0,1,.812.841h.616a2.074,2.074,0,0,0-.493-1,1.5,1.5,0,0,0-.935-.435v-.486h-.4v.478a1.794,1.794,0,0,0-.583.138,1.48,1.48,0,0,0-.471.315,1.48,1.48,0,0,0-.315.471,1.508,1.508,0,0,0-.116.6,1.508,1.508,0,0,0,.087.544.929.929,0,0,0,.272.38,1.7,1.7,0,0,0,.464.268,4.95,4.95,0,0,0,.663.207v1.92a1.3,1.3,0,0,1-.721-.355A1.079,1.079,0,0,1,1.116-11.744Zm1.42,1.123V-12.44q.2.058.37.127a1.1,1.1,0,0,1,.3.17.762.762,0,0,1,.2.243.765.765,0,0,1,.072.351,1.07,1.07,0,0,1-.069.4.742.742,0,0,1-.2.283.861.861,0,0,1-.3.17A1.483,1.483,0,0,1,2.536-10.621Zm-.4-4.261v1.7A2.683,2.683,0,0,1,1.8-13.3a1.123,1.123,0,0,1-.265-.156.613.613,0,0,1-.174-.225.768.768,0,0,1-.062-.322.791.791,0,0,1,.243-.627A1.031,1.031,0,0,1,2.138-14.882Z" transform="translate(4.5 63)" fill="#595959"/>
<rect width="48" height="37" fill="#9ccde4"/>
</g>
</defs>
<use href="#product_add_to_cart_item" y="5" x="5"/>
<use href="#product_add_to_cart_shirt" y="11" x="15"/>
<use href="#product_add_to_cart_item" y="5" x="66"/>
<use href="#product_add_to_cart_pants" y="11" x="83"/>
<use href="#product_add_to_cart_item" y="5" x="126"/>
<use href="#product_add_to_cart_shirt" y="11" x="137"/>
<use href="#product_add_to_cart_item" y="5" x="187"/>
<use href="#product_add_to_cart_watch" y="11" x="205"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -0,0 +1,43 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_banner_shirt">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_banner_watch">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_banner_pants">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_banner_item">
<rect width="230" height="60" fill="#fff"/>
<rect width="115" height="60" x="115" fill="#9ccde4"/>
<rect width="67" height="2" x="10" y="6" fill="#222"/>
<rect width="31" height="8" rx="4" x="10" y="16" fill="#3aadaa"/>
<rect width="15" height="2" x="21" y="19" fill="#fff"/>
<path d="M1.116-11.744H.5a1.827,1.827,0,0,0,.486,1.192,1.818,1.818,0,0,0,1.152.5v.594h.4v-.594a2.2,2.2,0,0,0,.638-.145,1.559,1.559,0,0,0,.5-.315A1.432,1.432,0,0,0,4-10.983a1.57,1.57,0,0,0,.12-.63A1.207,1.207,0,0,0,4-12.186a1.393,1.393,0,0,0-.308-.4,1.571,1.571,0,0,0-.377-.25,1.975,1.975,0,0,0-.341-.127l-.435-.116V-14.86a.964.964,0,0,1,.812.841h.616a2.074,2.074,0,0,0-.493-1,1.5,1.5,0,0,0-.935-.435v-.486h-.4v.478a1.794,1.794,0,0,0-.583.138,1.48,1.48,0,0,0-.471.315,1.48,1.48,0,0,0-.315.471,1.508,1.508,0,0,0-.116.6,1.508,1.508,0,0,0,.087.544.929.929,0,0,0,.272.38,1.7,1.7,0,0,0,.464.268,4.95,4.95,0,0,0,.663.207v1.92a1.3,1.3,0,0,1-.721-.355A1.079,1.079,0,0,1,1.116-11.744Zm1.42,1.123V-12.44q.2.058.37.127a1.1,1.1,0,0,1,.3.17.762.762,0,0,1,.2.243.765.765,0,0,1,.072.351,1.07,1.07,0,0,1-.069.4.742.742,0,0,1-.2.283.861.861,0,0,1-.3.17A1.483,1.483,0,0,1,2.536-10.621Zm-.4-4.261v1.7A2.683,2.683,0,0,1,1.8-13.3a1.123,1.123,0,0,1-.265-.156.613.613,0,0,1-.174-.225.768.768,0,0,1-.062-.322.791.791,0,0,1,.243-.627A1.031,1.031,0,0,1,2.138-14.882Z" transform="translate(13 33)" fill="#fff"/>
<rect width="75" height="1" x="10" y="32" fill="#666"/>
<rect width="66" height="1" x="10" y="38" fill="#666"/>
<rect width="62" height="1" x="10" y="35" fill="#666"/>
<rect width="46" height="1" x="10" y="41" fill="#666"/>
<rect width="28" height="7" x="10" y="48" rx="2" fill="#3aadaa"/>
<rect width="12" height="1" x="18" y="51" fill="#fff"/>
<rect width="12" height="1" x="47" y="51" fill="#3aadaa"/>
<use href="#product_banner_shirt" y="18" x="132"/>
<use href="#product_banner_watch" y="18" x="170"/>
<use href="#product_banner_pants" y="18" x="195"/>
</g>
</defs>
<use href="#product_banner_item" y="5" x="5"/>
</svg>

After

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -0,0 +1,38 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_borderless_1_shirt">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_borderless_1_watch">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_borderless_1_pants">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_borderless_1_item">
<rect width="48" height="44" fill="#9ccde4"/>
<rect width="42" height="2" transform="translate(3 49.514)" fill="#c7c7c7"/>
<rect width="15" height="2" transform="translate(9.024 56.117)" fill="#fff"/>
<path d="M1.116-11.744H.5a1.827,1.827,0,0,0,.486,1.192,1.818,1.818,0,0,0,1.152.5v.594h.4v-.594a2.2,2.2,0,0,0,.638-.145,1.559,1.559,0,0,0,.5-.315A1.432,1.432,0,0,0,4-10.983a1.57,1.57,0,0,0,.12-.63A1.207,1.207,0,0,0,4-12.186a1.393,1.393,0,0,0-.308-.4,1.571,1.571,0,0,0-.377-.25,1.975,1.975,0,0,0-.341-.127l-.435-.116V-14.86a.964.964,0,0,1,.812.841h.616a2.074,2.074,0,0,0-.493-1,1.5,1.5,0,0,0-.935-.435v-.486h-.4v.478a1.794,1.794,0,0,0-.583.138,1.48,1.48,0,0,0-.471.315,1.48,1.48,0,0,0-.315.471,1.508,1.508,0,0,0-.116.6,1.508,1.508,0,0,0,.087.544.929.929,0,0,0,.272.38,1.7,1.7,0,0,0,.464.268,4.95,4.95,0,0,0,.663.207v1.92a1.3,1.3,0,0,1-.721-.355A1.079,1.079,0,0,1,1.116-11.744Zm1.42,1.123V-12.44q.2.058.37.127a1.1,1.1,0,0,1,.3.17.762.762,0,0,1,.2.243.765.765,0,0,1,.072.351,1.07,1.07,0,0,1-.069.4.742.742,0,0,1-.2.283.861.861,0,0,1-.3.17A1.483,1.483,0,0,1,2.536-10.621Zm-.4-4.261v1.7A2.683,2.683,0,0,1,1.8-13.3a1.123,1.123,0,0,1-.265-.156.613.613,0,0,1-.174-.225.768.768,0,0,1-.062-.322.791.791,0,0,1,.243-.627A1.031,1.031,0,0,1,2.138-14.882Z" transform="translate(3 70)" fill="#fff"/>
</g>
</defs>
<use href="#product_borderless_1_item" y="5" x="5"/>
<use href="#product_borderless_1_shirt" y="15" x="15"/>
<use href="#product_borderless_1_item" y="5" x="66"/>
<use href="#product_borderless_1_pants" y="15" x="83"/>
<use href="#product_borderless_1_item" y="5" x="126"/>
<use href="#product_borderless_1_shirt" y="15" x="137"/>
<use href="#product_borderless_1_item" y="5" x="187"/>
<use href="#product_borderless_1_watch" y="15" x="205"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_borderless_2_shirt">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_borderless_2_watch">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_borderless_2_pants">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_borderless_2_item">
<rect width="48" height="37" fill="#9ccde4"/>
<rect width="42" height="2" x="3" y="47" fill="#c7c7c7"/>
<rect width="42" height="7" rx="2" x="3" y="53" fill="#3aadaa"/>
<rect width="16" height="1" x="16" y="56" fill="#ffffff"/>
<rect width="15" height="2" x="9" y="42" fill="#ffffff"/>
<path d="M1.116-11.744H.5a1.827,1.827,0,0,0,.486,1.192,1.818,1.818,0,0,0,1.152.5v.594h.4v-.594a2.2,2.2,0,0,0,.638-.145,1.559,1.559,0,0,0,.5-.315A1.432,1.432,0,0,0,4-10.983a1.57,1.57,0,0,0,.12-.63A1.207,1.207,0,0,0,4-12.186a1.393,1.393,0,0,0-.308-.4,1.571,1.571,0,0,0-.377-.25,1.975,1.975,0,0,0-.341-.127l-.435-.116V-14.86a.964.964,0,0,1,.812.841h.616a2.074,2.074,0,0,0-.493-1,1.5,1.5,0,0,0-.935-.435v-.486h-.4v.478a1.794,1.794,0,0,0-.583.138,1.48,1.48,0,0,0-.471.315,1.48,1.48,0,0,0-.315.471,1.508,1.508,0,0,0-.116.6,1.508,1.508,0,0,0,.087.544.929.929,0,0,0,.272.38,1.7,1.7,0,0,0,.464.268,4.95,4.95,0,0,0,.663.207v1.92a1.3,1.3,0,0,1-.721-.355A1.079,1.079,0,0,1,1.116-11.744Zm1.42,1.123V-12.44q.2.058.37.127a1.1,1.1,0,0,1,.3.17.762.762,0,0,1,.2.243.765.765,0,0,1,.072.351,1.07,1.07,0,0,1-.069.4.742.742,0,0,1-.2.283.861.861,0,0,1-.3.17A1.483,1.483,0,0,1,2.536-10.621Zm-.4-4.261v1.7A2.683,2.683,0,0,1,1.8-13.3a1.123,1.123,0,0,1-.265-.156.613.613,0,0,1-.174-.225.768.768,0,0,1-.062-.322.791.791,0,0,1,.243-.627A1.031,1.031,0,0,1,2.138-14.882Z" transform="translate(3 56)" fill="#ffffff"/>
</g>
</defs>
<use href="#product_borderless_2_item" y="5" x="5"/>
<use href="#product_borderless_2_shirt" y="12" x="15"/>
<use href="#product_borderless_2_item" y="5" x="66"/>
<use href="#product_borderless_2_pants" y="12" x="83"/>
<use href="#product_borderless_2_item" y="5" x="126"/>
<use href="#product_borderless_2_shirt" y="12" x="137"/>
<use href="#product_borderless_2_item" y="5" x="187"/>
<use href="#product_borderless_2_watch" y="12" x="205"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_card_group_shirt" transform="scale(.5)">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_card_group_watch" transform="scale(.5)">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_card_group_pants" transform="scale(.5)">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_card_group_item">
<rect width="115" height="30" fill="#fff" stroke="#c7c7c7" stroke-width="1"/>
<rect width="20" height="20" fill="#9ccde4" x="90" y="5"/>
<rect width="67" height="2" x="5" y="7" fill="#222"/>
<rect width="52" height="1" x="5" y="12" fill="#666"/>
<rect width="37" height="1" x="5" y="15" fill="#666"/>
<rect width="15" height="2" x="11" y="21" fill="#3aadaa"/>
<path d="M1.116-11.744H.5a1.827,1.827,0,0,0,.486,1.192,1.818,1.818,0,0,0,1.152.5v.594h.4v-.594a2.2,2.2,0,0,0,.638-.145,1.559,1.559,0,0,0,.5-.315A1.432,1.432,0,0,0,4-10.983a1.57,1.57,0,0,0,.12-.63A1.207,1.207,0,0,0,4-12.186a1.393,1.393,0,0,0-.308-.4,1.571,1.571,0,0,0-.377-.25,1.975,1.975,0,0,0-.341-.127l-.435-.116V-14.86a.964.964,0,0,1,.812.841h.616a2.074,2.074,0,0,0-.493-1,1.5,1.5,0,0,0-.935-.435v-.486h-.4v.478a1.794,1.794,0,0,0-.583.138,1.48,1.48,0,0,0-.471.315,1.48,1.48,0,0,0-.315.471,1.508,1.508,0,0,0-.116.6,1.508,1.508,0,0,0,.087.544.929.929,0,0,0,.272.38,1.7,1.7,0,0,0,.464.268,4.95,4.95,0,0,0,.663.207v1.92a1.3,1.3,0,0,1-.721-.355A1.079,1.079,0,0,1,1.116-11.744Zm1.42,1.123V-12.44q.2.058.37.127a1.1,1.1,0,0,1,.3.17.762.762,0,0,1,.2.243.765.765,0,0,1,.072.351,1.07,1.07,0,0,1-.069.4.742.742,0,0,1-.2.283.861.861,0,0,1-.3.17A1.483,1.483,0,0,1,2.536-10.621Zm-.4-4.261v1.7A2.683,2.683,0,0,1,1.8-13.3a1.123,1.123,0,0,1-.265-.156.613.613,0,0,1-.174-.225.768.768,0,0,1-.062-.322.791.791,0,0,1,.243-.627A1.031,1.031,0,0,1,2.138-14.882Z" transform="translate(4 35)" fill="#3aadaa"/>
</g>
</defs>
<use href="#product_card_group_item" y="5" x="5"/>
<use href="#product_card_group_shirt" y="14" x="99"/>
<use href="#product_card_group_item" y="5" x="120"/>
<use href="#product_card_group_watch" y="14" x="217"/>
<use href="#product_card_group_item" y="35" x="5"/>
<use href="#product_card_group_pants" y="44" x="102"/>
<use href="#product_card_group_item" y="35" x="120"/>
<use href="#product_card_group_shirt" y="44" x="214"/>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_centered_shirt">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_centered_watch">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_centered_pants">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_centered_item">
<rect width="48" height="43" y="17" fill="#fff"/>
<rect width="26" height="2" x="11" y="35" fill="#c7c7c7"/>
<rect width="15" height="2" x="18" y="42" fill="#595959"/>
<path d="M1.116-11.744H.5a1.827,1.827,0,0,0,.486,1.192,1.818,1.818,0,0,0,1.152.5v.594h.4v-.594a2.2,2.2,0,0,0,.638-.145,1.559,1.559,0,0,0,.5-.315A1.432,1.432,0,0,0,4-10.983a1.57,1.57,0,0,0,.12-.63A1.207,1.207,0,0,0,4-12.186a1.393,1.393,0,0,0-.308-.4,1.571,1.571,0,0,0-.377-.25,1.975,1.975,0,0,0-.341-.127l-.435-.116V-14.86a.964.964,0,0,1,.812.841h.616a2.074,2.074,0,0,0-.493-1,1.5,1.5,0,0,0-.935-.435v-.486h-.4v.478a1.794,1.794,0,0,0-.583.138,1.48,1.48,0,0,0-.471.315,1.48,1.48,0,0,0-.315.471,1.508,1.508,0,0,0-.116.6,1.508,1.508,0,0,0,.087.544.929.929,0,0,0,.272.38,1.7,1.7,0,0,0,.464.268,4.95,4.95,0,0,0,.663.207v1.92a1.3,1.3,0,0,1-.721-.355A1.079,1.079,0,0,1,1.116-11.744Zm1.42,1.123V-12.44q.2.058.37.127a1.1,1.1,0,0,1,.3.17.762.762,0,0,1,.2.243.765.765,0,0,1,.072.351,1.07,1.07,0,0,1-.069.4.742.742,0,0,1-.2.283.861.861,0,0,1-.3.17A1.483,1.483,0,0,1,2.536-10.621Zm-.4-4.261v1.7A2.683,2.683,0,0,1,1.8-13.3a1.123,1.123,0,0,1-.265-.156.613.613,0,0,1-.174-.225.768.768,0,0,1-.062-.322.791.791,0,0,1,.243-.627A1.031,1.031,0,0,1,2.138-14.882Z" transform="translate(10 56)" fill="#595959"/>
<rect width="34" height="7" rx="2" x="7" y="49" fill="#3aadaa"/>
<rect width="16" height="1" x="16" y="52" fill="#fff"/>
<rect width="34" height="32" fill="#9ccde4" x="7"/>
</g>
</defs>
<use href="#product_centered_item" y="5" x="5"/>
<use href="#product_centered_shirt" y="9" x="16"/>
<use href="#product_centered_item" y="5" x="66"/>
<use href="#product_centered_pants" y="9" x="83"/>
<use href="#product_centered_item" y="5" x="126"/>
<use href="#product_centered_shirt" y="9" x="137"/>
<use href="#product_centered_item" y="5" x="187"/>
<use href="#product_centered_watch" y="9" x="205"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_horizontal_card_shirt" transform="scale(.5)">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_horizontal_card_watch" transform="scale(.5)">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_horizontal_card_pants" transform="scale(.5)">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_horizontal_card_item">
<rect width="70" height="26" rx="3" fill="#fff"/>
<rect width="36" height="2" x="29" y="5" fill="#222"/>
<rect width="15" height="2" x="34" y="16" fill="#595959"/>
<path d="M1.116-11.744H.5a1.827,1.827,0,0,0,.486,1.192,1.818,1.818,0,0,0,1.152.5v.594h.4v-.594a2.2,2.2,0,0,0,.638-.145,1.559,1.559,0,0,0,.5-.315A1.432,1.432,0,0,0,4-10.983a1.57,1.57,0,0,0,.12-.63A1.207,1.207,0,0,0,4-12.186a1.393,1.393,0,0,0-.308-.4,1.571,1.571,0,0,0-.377-.25,1.975,1.975,0,0,0-.341-.127l-.435-.116V-14.86a.964.964,0,0,1,.812.841h.616a2.074,2.074,0,0,0-.493-1,1.5,1.5,0,0,0-.935-.435v-.486h-.4v.478a1.794,1.794,0,0,0-.583.138,1.48,1.48,0,0,0-.471.315,1.48,1.48,0,0,0-.315.471,1.508,1.508,0,0,0-.116.6,1.508,1.508,0,0,0,.087.544.929.929,0,0,0,.272.38,1.7,1.7,0,0,0,.464.268,4.95,4.95,0,0,0,.663.207v1.92a1.3,1.3,0,0,1-.721-.355A1.079,1.079,0,0,1,1.116-11.744Zm1.42,1.123V-12.44q.2.058.37.127a1.1,1.1,0,0,1,.3.17.762.762,0,0,1,.2.243.765.765,0,0,1,.072.351,1.07,1.07,0,0,1-.069.4.742.742,0,0,1-.2.283.861.861,0,0,1-.3.17A1.483,1.483,0,0,1,2.536-10.621Zm-.4-4.261v1.7A2.683,2.683,0,0,1,1.8-13.3a1.123,1.123,0,0,1-.265-.156.613.613,0,0,1-.174-.225.768.768,0,0,1-.062-.322.791.791,0,0,1,.243-.627A1.031,1.031,0,0,1,2.138-14.882Z" transform="translate(27.5 30)" fill="#595959"/>
<rect width="7" height="7" rx="2" x="58" y="14" fill="#3aadaa"/>
<rect width="20" height="20" fill="#9ccde4" x="5" y="3"/>
</g>
</defs>
<use href="#product_horizontal_card_item" y="5" x="5"/>
<use href="#product_horizontal_card_shirt" y="12" x="13"/>
<use href="#product_horizontal_card_item" y="5" x="85"/>
<use href="#product_horizontal_card_watch" y="12" x="97"/>
<use href="#product_horizontal_card_item" y="5" x="165"/>
<use href="#product_horizontal_card_pants" y="12" x="176"/>
<use href="#product_horizontal_card_item" y="39" x="5"/>
<use href="#product_horizontal_card_pants" y="46" x="16"/>
<use href="#product_horizontal_card_item" y="39" x="85"/>
<use href="#product_horizontal_card_shirt" y="46" x="94"/>
<use href="#product_horizontal_card_item" y="39" x="165"/>
<use href="#product_horizontal_card_watch" y="46" x="177"/>
</svg>

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -0,0 +1,38 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_horizontal_card_2_item">
<rect width="110" height="26" fill="#9ccde4"/>
<g transform="translate(10 4) scale(.75)">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g transform="translate(51 4) scale(.75)">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g transform="translate(83 4) scale(.75)">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<rect width="110" height="26" fill="#000" opacity="0.5"/>
<rect width="67" height="2" x="5" y="5" fill="#fff"/>
<rect width="52" height="1" x="5" y="10" fill="#fff"/>
<rect width="15" height="2" x="11" y="17" fill="#fff"/>
<path d="M1.116-11.744H.5a1.827,1.827,0,0,0,.486,1.192,1.818,1.818,0,0,0,1.152.5v.594h.4v-.594a2.2,2.2,0,0,0,.638-.145,1.559,1.559,0,0,0,.5-.315A1.432,1.432,0,0,0,4-10.983a1.57,1.57,0,0,0,.12-.63A1.207,1.207,0,0,0,4-12.186a1.393,1.393,0,0,0-.308-.4,1.571,1.571,0,0,0-.377-.25,1.975,1.975,0,0,0-.341-.127l-.435-.116V-14.86a.964.964,0,0,1,.812.841h.616a2.074,2.074,0,0,0-.493-1,1.5,1.5,0,0,0-.935-.435v-.486h-.4v.478a1.794,1.794,0,0,0-.583.138,1.48,1.48,0,0,0-.471.315,1.48,1.48,0,0,0-.315.471,1.508,1.508,0,0,0-.116.6,1.508,1.508,0,0,0,.087.544.929.929,0,0,0,.272.38,1.7,1.7,0,0,0,.464.268,4.95,4.95,0,0,0,.663.207v1.92a1.3,1.3,0,0,1-.721-.355A1.079,1.079,0,0,1,1.116-11.744Zm1.42,1.123V-12.44q.2.058.37.127a1.1,1.1,0,0,1,.3.17.762.762,0,0,1,.2.243.765.765,0,0,1,.072.351,1.07,1.07,0,0,1-.069.4.742.742,0,0,1-.2.283.861.861,0,0,1-.3.17A1.483,1.483,0,0,1,2.536-10.621Zm-.4-4.261v1.7A2.683,2.683,0,0,1,1.8-13.3a1.123,1.123,0,0,1-.265-.156.613.613,0,0,1-.174-.225.768.768,0,0,1-.062-.322.791.791,0,0,1,.243-.627A1.031,1.031,0,0,1,2.138-14.882Z" transform="translate(4.5 31)" fill="#fff"/>
<rect width="28" height="7" rx="2" x="77" y="14" fill="#3aadaa"/>
<rect width="12" height="1" x="85" y="17" fill="#fff"/>
</g>
</defs>
<use href="#product_horizontal_card_2_item" y="5" x="5"/>
<use href="#product_horizontal_card_2_item" y="5" x="125"/>
<use href="#product_horizontal_card_2_item" y="39" x="5"/>
<use href="#product_horizontal_card_2_item" y="39" x="125"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -0,0 +1,33 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_image_only_shirt">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_image_only_watch">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_image_only_pants">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<rect id="product_image_only_item" width="50" height="60" fill="#9ccde4"/>
</defs>
<use href="#product_image_only_item" y="5" x="5"/>
<use href="#product_image_only_item" y="5" x="65"/>
<use href="#product_image_only_item" y="5" x="125"/>
<use href="#product_image_only_item" y="5" x="185"/>
<use href="#product_image_only_shirt" y="23" x="17"/>
<use href="#product_image_only_pants" y="23" x="83"/>
<use href="#product_image_only_shirt" y="23" x="137"/>
<use href="#product_image_only_watch" y="23" x="205"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -0,0 +1,36 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_image_with_name_shirt">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_image_with_name_watch">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_image_with_name_pants">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_image_with_name_item">
<rect width="50" height="50" fill="#9ccde4"/>
<rect width="40" height="2" x="5" y="58" fill="#fff"/>
</g>
</defs>
<use href="#product_image_with_name_item" y="5" x="5"/>
<use href="#product_image_with_name_item" y="5" x="65"/>
<use href="#product_image_with_name_item" y="5" x="125"/>
<use href="#product_image_with_name_item" y="5" x="185"/>
<use href="#product_image_with_name_shirt" y="18" x="17"/>
<use href="#product_image_with_name_pants" y="18" x="83"/>
<use href="#product_image_with_name_shirt" y="18" x="137"/>
<use href="#product_image_with_name_watch" y="18" x="205"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -0,0 +1,37 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_image_with_price_shirt">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_image_with_price_watch">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_image_with_price_pants">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_image_with_price_item">
<rect width="50" height="50" fill="#9ccde4"/>
<rect width="15" height="2" x="30" y="58" fill="#fff"/>
<path d="M1.116-11.744H.5a1.827,1.827,0,0,0,.486,1.192,1.818,1.818,0,0,0,1.152.5v.594h.4v-.594a2.2,2.2,0,0,0,.638-.145,1.559,1.559,0,0,0,.5-.315A1.432,1.432,0,0,0,4-10.983a1.57,1.57,0,0,0,.12-.63A1.207,1.207,0,0,0,4-12.186a1.393,1.393,0,0,0-.308-.4,1.571,1.571,0,0,0-.377-.25,1.975,1.975,0,0,0-.341-.127l-.435-.116V-14.86a.964.964,0,0,1,.812.841h.616a2.074,2.074,0,0,0-.493-1,1.5,1.5,0,0,0-.935-.435v-.486h-.4v.478a1.794,1.794,0,0,0-.583.138,1.48,1.48,0,0,0-.471.315,1.48,1.48,0,0,0-.315.471,1.508,1.508,0,0,0-.116.6,1.508,1.508,0,0,0,.087.544.929.929,0,0,0,.272.38,1.7,1.7,0,0,0,.464.268,4.95,4.95,0,0,0,.663.207v1.92a1.3,1.3,0,0,1-.721-.355A1.079,1.079,0,0,1,1.116-11.744Zm1.42,1.123V-12.44q.2.058.37.127a1.1,1.1,0,0,1,.3.17.762.762,0,0,1,.2.243.765.765,0,0,1,.072.351,1.07,1.07,0,0,1-.069.4.742.742,0,0,1-.2.283.861.861,0,0,1-.3.17A1.483,1.483,0,0,1,2.536-10.621Zm-.4-4.261v1.7A2.683,2.683,0,0,1,1.8-13.3a1.123,1.123,0,0,1-.265-.156.613.613,0,0,1-.174-.225.768.768,0,0,1-.062-.322.791.791,0,0,1,.243-.627A1.031,1.031,0,0,1,2.138-14.882Z" transform="translate(23.5 72)" fill="#fff"/>
</g>
</defs>
<use href="#product_image_with_price_item" y="5" x="5"/>
<use href="#product_image_with_price_item" y="5" x="65"/>
<use href="#product_image_with_price_item" y="5" x="125"/>
<use href="#product_image_with_price_item" y="5" x="185"/>
<use href="#product_image_with_price_shirt" y="18" x="17"/>
<use href="#product_image_with_price_pants" y="18" x="83"/>
<use href="#product_image_with_price_shirt" y="18" x="137"/>
<use href="#product_image_with_price_watch" y="18" x="205"/>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View file

@ -0,0 +1,41 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="240" height="70" viewBox="0 0 240 70">
<defs>
<g id="product_view_detail_shirt">
<path d="M12.9.4H5.417L.4,6.928l2.86,3.683h1.7V22.9h15.86V10.611h1.726L25.4,6.928,20.383.4Z" fill="#e89849"/>
<path d="M4.954,10.612h-1.7L.4,6.928,5.417.4H20.383L25.4,6.928l-2.86,3.684H20.814" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M20.814,8.526V22.9H4.954V5.437" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M16.718,1.648a4.032,4.032,0,0,1-7.283.839" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="2.465" transform="translate(18.349 18.115)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line x1="6.068" transform="translate(14.746 19.742)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_view_detail_watch">
<rect width="4.743" height="7.122" x="3.4" y="15.7" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<rect width="4.743" height="7.122" x="3.4" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M11.124,11.642A5.361,5.361,0,1,1,5.762,6.28,5.362,5.362,0,0,1,11.124,11.642Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="3.183" transform="translate(5.762 8.459)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<line y1="1.313" x2="1.961" transform="translate(5.762 10.329)" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_view_detail_pants">
<path d="M13.848,22.9H8.884L7.123,7.094,5.361,22.9H.4V.4H13.848Z" fill="#e89849" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M2.231,4.648h2.46V8.157L2.566,9.215.4,8.077" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
<path d="M13.81,4.648H9.52V8.157l2.125,1.058" fill="none" stroke="#89440b" stroke-linecap="round" stroke-linejoin="round" stroke-width="1"/>
</g>
<g id="product_view_detail_item">
<rect width="70" height="60" fill="#fff"/>
<rect width="15" height="2" x="11" y="52" fill="#595959"/>
<path d="M1.116-11.744H.5a1.827,1.827,0,0,0,.486,1.192,1.818,1.818,0,0,0,1.152.5v.594h.4v-.594a2.2,2.2,0,0,0,.638-.145,1.559,1.559,0,0,0,.5-.315A1.432,1.432,0,0,0,4-10.983a1.57,1.57,0,0,0,.12-.63A1.207,1.207,0,0,0,4-12.186a1.393,1.393,0,0,0-.308-.4,1.571,1.571,0,0,0-.377-.25,1.975,1.975,0,0,0-.341-.127l-.435-.116V-14.86a.964.964,0,0,1,.812.841h.616a2.074,2.074,0,0,0-.493-1,1.5,1.5,0,0,0-.935-.435v-.486h-.4v.478a1.794,1.794,0,0,0-.583.138,1.48,1.48,0,0,0-.471.315,1.48,1.48,0,0,0-.315.471,1.508,1.508,0,0,0-.116.6,1.508,1.508,0,0,0,.087.544.929.929,0,0,0,.272.38,1.7,1.7,0,0,0,.464.268,4.95,4.95,0,0,0,.663.207v1.92a1.3,1.3,0,0,1-.721-.355A1.079,1.079,0,0,1,1.116-11.744Zm1.42,1.123V-12.44q.2.058.37.127a1.1,1.1,0,0,1,.3.17.762.762,0,0,1,.2.243.765.765,0,0,1,.072.351,1.07,1.07,0,0,1-.069.4.742.742,0,0,1-.2.283.861.861,0,0,1-.3.17A1.483,1.483,0,0,1,2.536-10.621Zm-.4-4.261v1.7A2.683,2.683,0,0,1,1.8-13.3a1.123,1.123,0,0,1-.265-.156.613.613,0,0,1-.174-.225.768.768,0,0,1-.062-.322.791.791,0,0,1,.243-.627A1.031,1.031,0,0,1,2.138-14.882Z" transform="translate(4.5 65)" fill="#595959"/>
<rect width="60" height="2" x="5" y="34" fill="#222"/>
<rect width="54" height="1" x="5" y="39" fill="#666"/>
<rect width="37" height="1" x="5" y="42" fill="#666"/>
<rect width="28" height="7" rx="2" x="37" y="50" fill="#3aadaa"/>
<rect width="12" height="1" x="45" y="53" fill="#fff"/>
<rect width="70" height="30" fill="#9ccde4"/>
</g>
</defs>
<use href="#product_view_detail_item" y="5" x="5"/>
<use href="#product_view_detail_item" y="5" x="85"/>
<use href="#product_view_detail_item" y="5" x="165"/>
<use href="#product_view_detail_shirt" y="8" x="27"/>
<use href="#product_view_detail_watch" y="8" x="114"/>
<use href="#product_view_detail_pants" y="8" x="193"/>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -0,0 +1,99 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="82" height="60" viewBox="0 0 82 60">
<defs>
<linearGradient id="linearGradient-1" x1="50%" x2="50%" y1="0%" y2="100%">
<stop offset="0%" stop-color="#00A09D"/>
<stop offset="100%" stop-color="#00E2FF"/>
</linearGradient>
<path id="path-2" d="M16 19v1H8v-1h8zm17 0v1h-9v-1h9zm16 0v1h-9v-1h9z"/>
<filter id="filter-3" width="102.4%" height="300%" x="-1.2%" y="-50%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/>
<feColorMatrix in="shadowOffsetOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.0995137675 0"/>
</filter>
<polygon id="path-4" points="0 8.954 5.571 11.28 5.571 4.714 0 2.571"/>
<filter id="filter-5" width="117.9%" height="123%" x="-9%" y="-5.7%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/>
<feColorMatrix in="shadowOffsetOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0"/>
</filter>
<polygon id="path-6" points="6.429 11.28 12 8.954 12 2.571 6.429 4.714"/>
<filter id="filter-7" width="117.9%" height="123%" x="-9%" y="-5.7%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/>
<feColorMatrix in="shadowOffsetOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.292012675 0"/>
</filter>
<polygon id="path-8" points="0 8.954 5.571 11.28 5.571 4.714 0 2.571"/>
<filter id="filter-9" width="117.9%" height="123%" x="-9%" y="-5.7%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/>
<feColorMatrix in="shadowOffsetOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0"/>
</filter>
<polygon id="path-10" points="6.429 11.28 12 8.954 12 2.571 6.429 4.714"/>
<filter id="filter-11" width="117.9%" height="123%" x="-9%" y="-5.7%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/>
<feColorMatrix in="shadowOffsetOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.292012675 0"/>
</filter>
<polygon id="path-12" points="0 8.954 5.571 11.28 5.571 4.714 0 2.571"/>
<filter id="filter-13" width="117.9%" height="123%" x="-9%" y="-5.7%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/>
<feColorMatrix in="shadowOffsetOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0"/>
</filter>
<polygon id="path-14" points="6.429 11.28 12 8.954 12 2.571 6.429 4.714"/>
<filter id="filter-15" width="117.9%" height="123%" x="-9%" y="-5.7%" filterUnits="objectBoundingBox">
<feOffset dy="1" in="SourceAlpha" result="shadowOffsetOuter1"/>
<feComposite in="shadowOffsetOuter1" in2="SourceAlpha" operator="out" result="shadowOffsetOuter1"/>
<feColorMatrix in="shadowOffsetOuter1" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.292012675 0"/>
</filter>
</defs>
<g fill="none" fill-rule="evenodd" class="snippets_thumbs">
<g class="s_products_recently_viewed">
<rect width="82" height="60" class="bg"/>
<g class="group" transform="translate(13 20)">
<path fill="url(#linearGradient-1)" d="M17.154 15v2H7v-2h10.154zm16.923 0v2h-11v-2h11zM51 15v2H38.308v-2H51z" class="combined_shape"/>
<g class="combined_shape">
<use fill="#000" filter="url(#filter-3)" xlink:href="#path-2"/>
<use fill="#FFF" fill-opacity=".348" xlink:href="#path-2"/>
</g>
<g class="box_solid" transform="translate(6)">
<rect width="12" height="11.143" class="rectangle"/>
<polygon fill="#FFF" fill-opacity=".78" points="6 .429 0 2.061 6 4.286 12 2.061" class="path"/>
<g class="path">
<use fill="#000" filter="url(#filter-5)" xlink:href="#path-4"/>
<use fill="#FFF" fill-opacity=".95" xlink:href="#path-4"/>
</g>
<g class="path">
<use fill="#000" filter="url(#filter-7)" xlink:href="#path-6"/>
<use fill="#FFF" fill-opacity=".78" xlink:href="#path-6"/>
</g>
</g>
<g class="box_solid" transform="translate(38)">
<rect width="12" height="11.143" class="rectangle"/>
<polygon fill="#FFF" fill-opacity=".78" points="6 .429 0 2.061 6 4.286 12 2.061" class="path"/>
<g class="path">
<use fill="#000" filter="url(#filter-9)" xlink:href="#path-8"/>
<use fill="#FFF" fill-opacity=".95" xlink:href="#path-8"/>
</g>
<g class="path">
<use fill="#000" filter="url(#filter-11)" xlink:href="#path-10"/>
<use fill="#FFF" fill-opacity=".78" xlink:href="#path-10"/>
</g>
</g>
<g class="box_solid" transform="translate(22)">
<rect width="12" height="11.143" class="rectangle"/>
<polygon fill="#FFF" fill-opacity=".78" points="6 .429 0 2.061 6 4.286 12 2.061" class="path"/>
<g class="path">
<use fill="#000" filter="url(#filter-13)" xlink:href="#path-12"/>
<use fill="#FFF" fill-opacity=".95" xlink:href="#path-12"/>
</g>
<g class="path">
<use fill="#000" filter="url(#filter-15)" xlink:href="#path-14"/>
<use fill="#FFF" fill-opacity=".78" xlink:href="#path-14"/>
</g>
</g>
<path fill="#FFF" stroke="#FFF" d="M1.5 4.793v4.414L-.707 7 1.5 4.793zm53-1L56.707 6 54.5 8.207V3.793z" class="combined_shape"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

View file

@ -0,0 +1,158 @@
/** @odoo-module **/
import { useWowlService } from '@web/legacy/utils';
import { Dialog } from "@web/core/dialog/dialog";
import { useHotkey } from "@web/core/hotkeys/hotkey_hook";
const { Component, onRendered, useRef, useEffect, useState, xml } = owl;
const ZOOM_STEP = 0.1;
export class ProductImageViewer extends Dialog {
setup() {
super.setup();
this.imageContainerRef = useRef("imageContainer");
this.images = [...this.props.images].map(image => {
return {
src: image.dataset.zoomImage || image.src,
thumbnailSrc: image.src.replace('/image_1024/', '/image_128/'),
};
});
this.state = useState({
selectedImageIdx: this.props.selectedImageIdx || 0,
imageScale: 1,
});
this.isDragging = false;
this.dragStartPos = { x: 0, y: 0 };
// Doing a full render for the translate is too slow.
this.imageTranslate = { x: 0, y: 0 };
useHotkey("arrowleft", this.previousImage.bind(this));
useHotkey("arrowright", this.nextImage.bind(this));
useHotkey("r", () => {
this.imageTranslate = { x: 0, y: 0 };
this.isDragging = false;
this.state.imageScale = 1;
this.updateImage();
});
// Not using a t-on-click on purpose because we want to be able to cancel the drag
// when we go outside of the window.
useEffect(
(document) => {
const onGlobalClick = this.onGlobalClick.bind(this);
document.addEventListener("click", onGlobalClick);
return () => {document.removeEventListener("click", onGlobalClick)};
},
() => [document],
);
// For some reason the styling does not always update properly.
onRendered(() => {
this.updateImage();
})
}
get selectedImage() {
return this.images[this.state.selectedImageIdx];
}
set selectedImage(image) {
this.state.imageScale = 1;
this.imageTranslate = { x: 0, y: 0 };
this.state.selectedImageIdx = this.images.indexOf(image);
}
get imageStyle() {
return `transform:
scale3d(${this.state.imageScale}, ${this.state.imageScale}, 1);
`;
}
get imageContainerStyle() {
return `transform: translate(${this.imageTranslate.x}px, ${this.imageTranslate.y}px);`;
}
previousImage() {
this.selectedImage = this.images[(this.state.selectedImageIdx - 1 + this.images.length) % this.images.length];
}
nextImage() {
this.selectedImage = this.images[(this.state.selectedImageIdx + 1) % this.images.length];
}
updateImage() {
if (!this.imageContainerRef || !this.imageContainerRef.el) {
return;
}
this.imageContainerRef.el.style = this.imageContainerStyle;
}
onGlobalClick(ev) {
if (ev.target.tagName === "IMG") {
// Only zoom if the image did not move
if (this.dragStartPos.clientX === ev.clientX && this.dragStartPos.clientY === ev.clientY) {
this.zoomIn(ZOOM_STEP * 3);
}
}
if (ev.target.classList.contains('o_wsale_image_viewer_void') && !this.isDragging) {
ev.stopPropagation();
ev.preventDefault();
this.data.close();
} else {
this.isDragging = false;
}
}
zoomIn(step=undefined) {
this.state.imageScale += step || ZOOM_STEP;
}
zoomOut(step=undefined) {
this.state.imageScale = Math.max(0.5, this.state.imageScale - (step || ZOOM_STEP));
}
onWheelImage(ev) {
if (ev.deltaY > 0) {
this.zoomOut();
} else {
this.zoomIn();
}
}
onMousedownImage(ev) {
this.isDragging = true;
this.dragStartPos = {
x: ev.clientX - this.imageTranslate.x,
y: ev.clientY - this.imageTranslate.y,
clientX: ev.clientX,
clientY: ev.clientY,
};
}
onGlobalMousemove(ev) {
if (!this.isDragging) {
return;
}
this.imageTranslate.x = ev.clientX - this.dragStartPos.x;
this.imageTranslate.y = ev.clientY - this.dragStartPos.y;
this.updateImage();
}
}
ProductImageViewer.props = {
...Dialog.props,
images: { type: NodeList, required: true },
selectedImageIdx: { type: Number, optional: true },
close: Function,
};
delete ProductImageViewer.props.slots;
ProductImageViewer.template = "website_sale.ProductImageViewer";
export class ProductImageViewerWrapper extends Component {
setup() {
this.dialogs = useWowlService('dialog');
onRendered(() => {
this.dialogs.add(ProductImageViewer, this.props);
});
}
}
ProductImageViewerWrapper.template = xml``;

View file

@ -0,0 +1,32 @@
/** @odoo-module **/
import { WysiwygAdapterComponent } from '@website/components/wysiwyg_adapter/wysiwyg_adapter';
import { patch } from 'web.utils';
// TODO this whole patch actually seems unnecessary. The bug it solved seems
// to stay solved if this is removed. To investigate.
patch(WysiwygAdapterComponent.prototype, 'website_sale_wysiwyg_adapter', {
/**
* @override
*/
_getContentEditableAreas() {
const array = this._super(...arguments);
return array.filter(el => {
// TODO should really review this system of "ContentEditableAreas +
// ReadOnlyAreas", here the "products_header" stuff is duplicated in
// both but this system is also duplicated with o_not_editable and
// maybe even other systems (like preserving contenteditable="false"
// with oe-keep-contenteditable).
return !el.closest('.oe_website_sale .products_header');
});
},
/**
* @override
*/
_getReadOnlyAreas() {
const readOnlyEls = this._super(...arguments);
return [...readOnlyEls].concat(
$(this.websiteService.pageDocument).find("#wrapwrap").find('.oe_website_sale .products_header, .oe_website_sale .products_header a').toArray()
);
},
});

View file

@ -0,0 +1,15 @@
/** @odoo-module **/
import { NewContentModal, MODULE_STATUS } from '@website/systray_items/new_content';
import { patch } from 'web.utils';
patch(NewContentModal.prototype, 'website_sale_new_content', {
setup() {
this._super();
const newProductElement = this.state.newContentElements.find(element => element.moduleXmlId === 'base.module_website_sale');
newProductElement.createNewContent = () => this.onAddContent('website_sale.product_product_action_add', true);
newProductElement.status = MODULE_STATUS.INSTALLED;
newProductElement.model = 'product.product';
},
});

View file

@ -0,0 +1,120 @@
odoo.define("website_sale.tour_utils", function (require) {
"use strict";
const core = require("web.core");
const _t = core._t;
const wTourUtils = require('website.tour_utils');
function addToCart({productName, search = true, productHasVariants = false}) {
const steps = [];
if (search) {
steps.push(...searchProduct(productName));
}
steps.push(wTourUtils.clickOnElement(productName, `a:contains(${productName})`));
steps.push(wTourUtils.clickOnElement('Add to cart', '#add_to_cart'));
if (productHasVariants) {
steps.push(wTourUtils.clickOnElement('Continue Shopping', 'button:contains("Continue Shopping")'));
}
return steps;
}
function assertCartContains({productName, backend, notContains = false} = {}) {
let trigger = `a:contains(${productName})`;
if (notContains) {
trigger = `:not(${trigger})`;
}
return {
content: `Checking if ${productName} is in the cart`,
trigger: `${backend ? "iframe" : ""} ${trigger}`,
run: () => {}
};
}
/**
* Used to assert if the price attribute of a given product is correct on the /shop view
*/
function assertProductPrice(attribute, value, productName) {
return {
content: `The ${attribute} of the ${productName} is ${value}`,
trigger: `div:contains("${productName}") [data-oe-expression="template_price_vals['${attribute}']"] .oe_currency_value:contains("${value}")`,
run: () => {}
};
}
function fillAdressForm(adressParams = {
name: "John Doe",
phone: "123456789",
email: "johndoe@gmail.com",
street: "1 rue de la paix",
city: "Paris",
zip: "75000"
}) {
let steps = [];
steps.push({
content: "Address filling",
trigger: 'select[name="country_id"]',
run: () => {
$('input[name="name"]').val(adressParams.name);
$('input[name="phone"]').val(adressParams.phone);
$('input[name="email"]').val(adressParams.email);
$('input[name="street"]').val(adressParams.street);
$('input[name="city"]').val(adressParams.city);
$('input[name="zip"]').val(adressParams.zip);
$('#country_id option:eq(1)').attr('selected', true);
}
});
steps.push({
content: "Next",
trigger: '.oe_cart .btn:contains("Next")',
});
return steps;
}
function goToCart({quantity = 1, position = "bottom", backend = false} = {}) {
return {
content: _t("Go to cart"),
trigger: `${backend ? "iframe" : ""} a:has(.my_cart_quantity:containsExact(${quantity}))`,
position: position,
run: "click",
};
}
function searchProduct(productName) {
return [
wTourUtils.clickOnElement('Shop', 'a:contains("Shop")'),
{
content: "Search for the product",
trigger: 'form input[name="search"]',
run: `text ${productName}`
},
wTourUtils.clickOnElement('Search', 'form:has(input[name="search"]) .oe_search_button'),
];
}
/**
* Used to select a pricelist on the /shop view
*/
function selectPriceList(pricelist) {
return [
{
content: "Click on pricelist dropdown",
trigger: "div.o_pricelist_dropdown a[data-bs-toggle=dropdown]",
},
{
content: "Click on pricelist",
trigger: `span:contains(${pricelist})`,
},
];
}
return {
addToCart,
assertCartContains,
assertProductPrice,
fillAdressForm,
goToCart,
selectPriceList,
searchProduct,
};
});

View file

@ -0,0 +1,83 @@
odoo.define("website_sale.tour_shop", function (require) {
"use strict";
const {_t} = require("web.core");
const {Markup} = require('web.utils');
const wTourUtils = require("website.tour_utils");
wTourUtils.registerWebsitePreviewTour("shop", {
url: '/shop',
sequence: 130,
}, [{
trigger: ".o_menu_systray .o_new_content_container > a",
content: _t("Let's create your first product."),
extra_trigger: "iframe .js_sale",
consumeVisibleOnly: true,
position: "bottom",
}, {
trigger: "a[data-module-xml-id='base.module_website_sale']",
content: Markup(_t("Select <b>New Product</b> to create it and manage its properties to boost your sales.")),
position: "bottom",
}, {
trigger: ".modal-dialog input[type=text]",
content: _t("Enter a name for your new product"),
position: "left",
}, {
trigger: ".modal-footer button.btn-primary",
content: Markup(_t("Click on <em>Save</em> to create the product.")),
position: "right",
}, {
trigger: "iframe .product_price .oe_currency_value:visible",
extra_trigger: "#oe_snippets.o_loaded",
content: _t("Edit the price of this product by clicking on the amount."),
position: "bottom",
run: "text 1.99",
timeout: 30000,
}, {
trigger: "iframe #wrap img.product_detail_img",
extra_trigger: "iframe .product_price .o_dirty .oe_currency_value:not(:containsExact(1.00))",
content: _t("Double click here to set an image describing your product."),
position: "top",
run: function (actions) {
actions.dblclick();
},
}, {
trigger: ".o_select_media_dialog .o_upload_media_button",
content: _t("Upload a file from your local library."),
position: "bottom",
run: function (actions) {
actions.auto(".modal-footer .btn-secondary");
},
auto: true,
}, {
trigger: "button.o_we_add_snippet_btn",
auto: true,
}, {
trigger: "#snippet_structure .oe_snippet:eq(3) .oe_snippet_thumbnail",
extra_trigger: "body:not(.modal-open)",
content: _t("Drag this website block and drop it in your page."),
position: "bottom",
run: "drag_and_drop",
}, {
trigger: "button[data-action=save]",
content: Markup(_t("Once you click on <b>Save</b>, your product is updated.")),
position: "bottom",
// Wait until the drag and drop is resolved (causing a history step)
// before clicking save.
extra_trigger: ".o_we_external_history_buttons button[data-action=undo]:not([disabled])",
}, {
trigger: ".o_menu_systray_item .o_switch_danger_success",
extra_trigger: "iframe body:not(.editor_enable)",
content: _t("Click on this button so your customers can see it."),
position: "bottom",
}, {
trigger: "button[data-menu-xmlid='website.menu_reporting']",
content: _t("Click here to open the reporting menu"),
position: "bottom",
}, {
trigger: "a[data-menu-xmlid='website.menu_website_dashboard'], a[data-menu-xmlid='website.menu_website_analytics']",
content: _t("Let's now take a look at your eCommerce dashboard to get your eCommerce website ready in no time."),
position: "bottom",
run: "click",
}]);
});

View file

@ -0,0 +1,85 @@
odoo.define('website_sale.VariantMixin', function (require) {
'use strict';
var VariantMixin = require('sale.VariantMixin');
/**
* Website behavior is slightly different from backend so we append
* "_website" to URLs to lead to a different route
*
* @private
* @param {string} uri The uri to adapt
*/
VariantMixin._getUri = function (uri) {
if (this.isWebsite) {
return uri + '_website';
} else {
return uri;
}
};
const originalOnChangeCombination = VariantMixin._onChangeCombination;
VariantMixin._onChangeCombination = function (ev, $parent, combination) {
const $pricePerUom = $parent.find(".o_base_unit_price:first .oe_currency_value");
if ($pricePerUom) {
if (combination.is_combination_possible !== false && combination.base_unit_price != 0) {
$pricePerUom.parents(".o_base_unit_price_wrapper").removeClass("d-none");
$pricePerUom.text(this._priceToStr(combination.base_unit_price));
$parent.find(".oe_custom_base_unit:first").text(combination.base_unit_name);
} else {
$pricePerUom.parents(".o_base_unit_price_wrapper").addClass("d-none");
}
}
// Triggers a new JS event with the correct payload, which is then handled
// by the google analytics tracking code.
// Indeed, every time another variant is selected, a new view_item event
// needs to be tracked by google analytics.
if ('product_tracking_info' in combination) {
const $product = $('#product_detail');
$product.data('product-tracking-info', combination['product_tracking_info']);
$product.trigger('view_item_event', combination['product_tracking_info']);
}
const addToCart = $parent.find('#add_to_cart_wrap');
const contactUsButton = $parent.find('#contact_us_wrapper');
const productPrice = $parent.find('.product_price');
const quantity = $parent.find('.css_quantity');
const product_unavailable = $parent.find('#product_unavailable');
if (combination.prevent_zero_price_sale) {
productPrice.removeClass('d-inline-block').addClass('d-none');
quantity.removeClass('d-inline-flex').addClass('d-none');
addToCart.removeClass('d-inline-flex').addClass('d-none');
contactUsButton.removeClass('d-none').addClass('d-flex');
product_unavailable.removeClass('d-none').addClass('d-flex')
} else {
productPrice.removeClass('d-none').addClass('d-inline-block');
quantity.removeClass('d-none').addClass('d-inline-flex');
addToCart.removeClass('d-none').addClass('d-inline-flex');
contactUsButton.removeClass('d-flex').addClass('d-none');
product_unavailable.removeClass('d-flex').addClass('d-none')
}
originalOnChangeCombination.apply(this, [ev, $parent, combination]);
};
const originalToggleDisable = VariantMixin._toggleDisable;
/**
* Toggles the disabled class depending on the $parent element
* and the possibility of the current combination. This override
* allows us to disable the secondary button in the website
* sale product configuration modal.
*
* @private
* @param {$.Element} $parent
* @param {boolean} isCombinationPossible
*/
VariantMixin._toggleDisable = function ($parent, isCombinationPossible) {
if ($parent.hasClass('in_cart')) {
const secondaryButton = $parent.parents('.modal-content').find('.modal-footer .btn-secondary');
secondaryButton.prop('disabled', !isCombinationPossible);
secondaryButton.toggleClass('disabled', !isCombinationPossible);
}
originalToggleDisable.apply(this, [$parent, isCombinationPossible]);
};
return VariantMixin;
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,127 @@
odoo.define('website_sale.backend', function (require) {
"use strict";
var WebsiteBackend = require('website.backend.dashboard');
var COLORS = ['#875a7b', '#21b799', '#E4A900', '#D5653E', '#5B899E', '#E46F78', '#8F8F8F'];
WebsiteBackend.include({
jsLibs: [
'/web/static/lib/Chart/Chart.js',
],
events: _.defaults({
'click tr.o_product_template': 'on_product_template',
'click .js_utm_selector': '_onClickUtmButton',
}, WebsiteBackend.prototype.events),
init: function (parent, context) {
this._super(parent, context);
this.graphs.push({'name': 'sales', 'group': 'sale_salesman'});
},
/**
* @override method from website backendDashboard
* @private
*/
render_graphs: function() {
this._super();
this.utmGraphData = this.dashboards_data.sales.utm_graph;
this.utmGraphData && this._renderUtmGraph();
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Method used to generate Pie chart, depending on user selected UTM option(campaign, medium, source)
*
* @private
*/
_renderUtmGraph: function() {
var self = this;
this.$(".utm_button_name").html(this.btnName); // change drop-down button name
var utmDataType = this.utmType || 'campaign_id';
var graphData = this.utmGraphData[utmDataType];
if (graphData.length) {
this.$(".o_utm_no_data_img").hide();
this.$(".o_utm_data_graph").empty().show();
var $canvas = $('<canvas/>');
this.$(".o_utm_data_graph").append($canvas);
var context = $canvas[0].getContext('2d');
console.log(graphData);
var data = [];
var labels = [];
graphData.forEach(function(pt) {
data.push(pt.amount_total);
labels.push(pt.utm_type);
});
var config = {
type: 'pie',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: COLORS,
}]
},
options: {
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var label = data.labels[tooltipItem.index] || '';
if (label) {
label += ': ';
}
var amount = data.datasets[0].data[tooltipItem.index];
amount = self.render_monetary_field(amount, self.data.currency);
label += amount;
return label;
}
}
},
legend: {display: false}
}
};
new Chart(context, config);
} else {
this.$(".o_utm_no_data_img").show();
this.$(".o_utm_data_graph").hide();
}
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Onchange on UTM dropdown button, this method is called.
*
* @private
*/
_onClickUtmButton: function(ev) {
this.utmType = $(ev.currentTarget).attr('name');
this.btnName = $(ev.currentTarget).text();
this._renderUtmGraph();
},
on_product_template: function (ev) {
ev.preventDefault();
var product_tmpl_id = $(ev.currentTarget).data('productId');
this.do_action({
type: 'ir.actions.act_window',
res_model: 'product.template',
res_id: product_tmpl_id,
views: [[false, 'form']],
target: 'current',
}, {
on_reverse_breadcrumb: this.on_reverse_breadcrumb,
});
},
});
return WebsiteBackend;
});

View file

@ -0,0 +1,18 @@
/** @odoo-module **/
import * as publicWidget from 'web.public.widget'
publicWidget.registry.ProductCategoriesLinks = publicWidget.Widget.extend({
selector: '.o_wsale_products_page',
events: {
'click [data-link-href]': '_openLink',
},
_openLink: function (ev) {
const productsDiv = this.el.querySelector('.o_wsale_products_grid_table_wrapper');
if (productsDiv) {
productsDiv.classList.add('opacity-50');
}
window.location.href = ev.currentTarget.getAttribute('data-link-href');
},
});

View file

@ -0,0 +1,35 @@
odoo.define('website_sale.form', function (require) {
'use strict';
const core = require('web.core');
var FormEditorRegistry = require('website.form_editor_registry');
const _lt = core._lt;
FormEditorRegistry.add('create_customer', {
formFields: [{
type: 'char',
modelRequired: true,
name: 'name',
fillWith: 'name',
string: _lt('Your Name'),
}, {
type: 'email',
required: true,
fillWith: 'email',
name: 'email',
string: _lt('Your Email'),
}, {
type: 'tel',
fillWith: 'phone',
name: 'phone',
string: _lt('Phone Number'),
}, {
type: 'char',
name: 'company_name',
fillWith: 'commercial_company_name',
string: _lt('Company Name'),
}],
});
});

View file

@ -0,0 +1,126 @@
odoo.define('website_sale.payment', require => {
'use strict';
const checkoutForm = require('payment.checkout_form');
const publicWidget = require('web.public.widget');
const websiteSalePaymentMixin = {
/**
* @override
*/
init: function () {
this._onClickTCCheckbox = _.debounce(this._onClickTCCheckbox, 100, true);
this._super(...arguments);
},
/**
* @override
*/
start: function () {
this.$checkbox = this.$('#checkbox_tc');
this.$submitButton = this.$('button[name="o_payment_submit_button"]');
this._adaptConfirmButton();
return this._super(...arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Update the data on the submit button with the status of the Terms and Conditions input.
*
* @private
* @return {undefined}
*/
_adaptConfirmButton: function () {
if (this.$checkbox.length > 0) {
const disabledReasons = this.$submitButton.data('disabled_reasons') || {};
disabledReasons.tc = !this.$checkbox.prop('checked');
this.$submitButton.data('disabled_reasons', disabledReasons);
}
},
};
checkoutForm.include(Object.assign({}, websiteSalePaymentMixin, {
events: Object.assign({}, checkoutForm.prototype.events, {
'change #checkbox_tc': '_onClickTCCheckbox',
}),
//----------------------------------------------------------------------
// Private
//----------------------------------------------------------------------
/**
* Verify that the Terms and Condition checkbox is checked.
*
* @override method from payment.payment_form_mixin
* @private
* @return {boolean} Whether the submit button can be enabled
*/
_isButtonReady: function () {
const disabledReasonFound = _.contains(
this.$submitButton.data('disabled_reasons'), true
);
return !disabledReasonFound && this._super();
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Enable the submit button if it all conditions are met.
*
* @private
* @return {undefined}
*/
_onClickTCCheckbox: function () {
this._adaptConfirmButton();
if (!this._enableButton()) {
this._disableButton(false);
}
},
}));
publicWidget.registry.WebsiteSalePayment = publicWidget.Widget.extend(
Object.assign({}, websiteSalePaymentMixin, {
selector: 'div[name="o_website_sale_free_cart"]',
events: {
'change #checkbox_tc': '_onClickTCCheckbox',
},
/**
* @override
*/
start: function () {
this.$checkbox = this.$('#checkbox_tc');
this.$submitButton = this.$('button[name="o_payment_submit_button"]');
this._onClickTCCheckbox();
return this._super(...arguments);
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Enable the submit button if it all conditions are met.
*
* @private
* @return {undefined}
*/
_onClickTCCheckbox: function () {
this._adaptConfirmButton();
const disabledReasonFound = _.contains(
this.$submitButton.data('disabled_reasons'), true
);
this.$submitButton.prop('disabled', disabledReasonFound);
},
}));
});

View file

@ -0,0 +1,65 @@
odoo.define('website_sale.recently_viewed', function (require) {
var publicWidget = require('web.public.widget');
const {getCookie, setCookie} = require('web.utils.cookies');
publicWidget.registry.productsRecentlyViewedUpdate = publicWidget.Widget.extend({
selector: '#product_detail',
events: {
'change input.product_id[name="product_id"]': '_onProductChange',
},
debounceValue: 500,
/**
* @constructor
*/
init: function () {
this._super.apply(this, arguments);
this._onProductChange = _.debounce(this._onProductChange, this.debounceValue);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Debounced method that wait some time before marking the product as viewed.
* @private
* @param {HTMLInputElement} $input
*/
_updateProductView: function ($input) {
var productId = parseInt($input.val());
var cookieName = 'seen_product_id_' + productId;
if (! parseInt(this.el.dataset.viewTrack, 10)) {
return; // Is not tracked
}
if (getCookie(cookieName)) {
return; // Already tracked in the last 30min
}
if ($(this.el).find('.js_product.css_not_available').length) {
return; // Variant not possible
}
this._rpc({
route: '/shop/products/recently_viewed_update',
params: {
product_id: productId,
}
}).then(function (res) {
setCookie(cookieName, productId, 30 * 60, 'optional');
});
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Call debounced method when product change to reset timer.
* @private
* @param {Event} ev
*/
_onProductChange: function (ev) {
this._updateProductView($(ev.currentTarget));
},
});
});

View file

@ -0,0 +1,241 @@
/** @odoo-module **/
import { debounce as debounceFn } from "@web/core/utils/timing";
import publicWidget from "web.public.widget";
import { localization as l10n } from "@web/core/l10n/localization";
import { ComponentWrapper } from "web.OwlCompatibility";
import { intersperse, nbsp } from "@web/core/utils/strings";
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
/**
* Inserts "thousands" separators in the provided number.
*
* @private
* @param {string} string representing integer number
* @param {string} [thousandsSep=","] the separator to insert
* @param {number[]} [grouping=[]]
* array of relative offsets at which to insert `thousandsSep`.
* See `strings.intersperse` method.
* @returns {string}
*/
function insertThousandsSep(number, thousandsSep = ",", grouping = []) {
const negative = number[0] === "-";
number = negative ? number.slice(1) : number;
return (negative ? "-" : "") + intersperse(number, grouping, thousandsSep);
}
export function formatFloat(value, digits = 2) {
if (value === false) {
return "";
}
const grouping = l10n.grouping;
const thousandsSep = l10n.thousandsSep;
const decimalPoint = l10n.decimalPoint;
let precision = digits;
const formatted = (value || 0).toFixed(precision).split(".");
formatted[0] = insertThousandsSep(formatted[0], thousandsSep, grouping);
return formatted[1] ? formatted.join(decimalPoint) : formatted[0];
}
export function formatMonetary(value, currency) {
// Monetary fields want to display nothing when the value is unset.
// You wouldn't want a value of 0 euro if nothing has been provided.
if (value === false) {
return "";
}
const digits = (currency && currency.decimal_places) || 2;
let formattedValue = formatFloat(value, digits);
if (!currency) {
return formattedValue;
}
const formatted = [currency.symbol, formattedValue];
if (currency.position === "after") {
formatted.reverse();
}
return formatted.join(nbsp);
}
// Widget responsible for openingn the modal (giving out the sale order id)
publicWidget.registry.SaleOrderPortalReorderWidget = publicWidget.Widget.extend({
selector: ".o_portal_sidebar",
events: {
"click .o_wsale_reorder_button": "_onReorder",
},
_onReorder(ev) {
const orderId = parseInt(ev.currentTarget.dataset.saleOrderId);
const urlSearchParams = new URLSearchParams(window.location.search);
if (!orderId || !urlSearchParams.has("access_token")) {
return;
}
// Open the modal
const dialogWrapper = new ComponentWrapper(this, ReorderDialogWrapper, {
orderId: orderId,
accessToken: urlSearchParams.get("access_token"),
});
dialogWrapper.mount(document.body);
},
});
import { useService } from "@web/core/utils/hooks";
import { Dialog } from "@web/core/dialog/dialog";
const { Component, onRendered, onWillStart, xml } = owl;
// Reorder Dialog
export class ReorderDialogWrapper extends Component {
setup() {
this.dialogService = useService("dialog");
onRendered(() => {
this.dialogService.add(ReorderDialog, this.props);
});
}
}
ReorderDialogWrapper.template = xml``;
export class ReorderConfirmationDialog extends ConfirmationDialog {
/**
* @override
*
* In ConfirmationDialog class cancel button and close button's behavior is the same
* so we need to override the default behavior of close button. Because on cancel
* we add products to cart without clearing the cart.
* */
setup() {
super.setup();
this.env.dialogData.close = () => this.props.close();
}
}
ReorderConfirmationDialog.template = "website_sale.ReorderConfirmationDialog";
export class ReorderDialog extends Component {
setup() {
this.rpc = useService("rpc");
this.orm = useService("orm");
this.dialogService = useService("dialog");
this.formatMonetary = formatMonetary;
onWillStart(this.onWillStartHandler.bind(this));
}
async onWillStartHandler() {
// Cart Qty should not change while the dialog is opened.
this.cartQty = parseInt(sessionStorage.getItem("website_sale_cart_quantity"));
if (!this.cartQty) {
this.cartQty = await this.rpc("/shop/cart/quantity");
}
// Get required information about the order
this.content = await this.rpc("/my/orders/reorder_modal_content", {
order_id: this.props.orderId,
access_token: this.props.accessToken,
});
// Get required information about each products
for (const product of this.content.products) {
product.debouncedLoadProductCombinationInfo = debounceFn(() => {
this.loadProductCombinationInfo(product).then(this.render.bind(this));
}, 200);
}
}
get total() {
return this.content.products.reduce((total, product) => {
if (product.add_to_cart_allowed) {
total += product.combinationInfo.price * product.qty;
}
return total;
}, 0);
}
get hasBuyableProducts() {
return this.content.products.some((product) => product.add_to_cart_allowed);
}
async loadProductCombinationInfo(product) {
product.combinationInfo = await this.rpc("/sale/get_combination_info_website", {
product_template_id: product.product_template_id,
product_id: product.product_id,
combination: product.combination,
add_qty: product.qty,
pricelist_id: false,
context: {
website_sale_no_images: true,
},
});
}
getWarningForProduct(product) {
if (!product.add_to_cart_allowed) {
return this.env._t("This product is not available for purchase.");
}
return false;
}
changeProductQty(product, newQty) {
const productNewQty = Math.max(0, newQty);
const qtyChanged = productNewQty !== product.qty;
product.qty = productNewQty;
this.render(true);
if (!qtyChanged) {
return;
}
product.debouncedLoadProductCombinationInfo();
}
onChangeProductQtyInput(ev, product) {
const newQty = parseFloat(ev.target.value) || product.qty;
this.changeProductQty(product, newQty);
}
async confirmReorder(ev) {
if (this.confirmed) {
return;
}
this.confirmed = true;
const onConfirm = async () => {
await this.addProductsToCart();
window.location = "/shop/cart";
};
if (this.cartQty) {
// Open confirmation modal
this.dialogService.add(ReorderConfirmationDialog, {
body: this.env._t("Do you wish to clear your cart before adding products to it?"),
confirm: async () => {
await this.rpc("/shop/cart/clear");
await onConfirm();
},
cancel: onConfirm,
});
} else {
await onConfirm();
}
}
async addProductsToCart() {
for (const product of this.content.products) {
if (!product.add_to_cart_allowed) {
continue;
}
await this.rpc("/shop/cart/update_json", {
product_id: product.product_id,
add_qty: product.qty,
no_variant_attribute_values: JSON.stringify(product.no_variant_attribute_values),
product_custom_attribute_values: JSON.stringify(product.product_custom_attribute_values),
});
}
}
}
ReorderDialog.props = {
close: Function,
orderId: Number,
accessToken: String,
};
ReorderDialog.components = {
Dialog,
};
ReorderDialog.template = "website_sale.ReorderModal";

View file

@ -0,0 +1,122 @@
odoo.define('website_sale.tracking', function (require) {
var publicWidget = require('web.public.widget');
publicWidget.registry.websiteSaleTracking = publicWidget.Widget.extend({
selector: '.oe_website_sale',
events: {
'click form[action="/shop/cart/update"] a.a-submit': '_onAddProductIntoCart',
'click a[href="/shop/checkout"]': '_onCheckoutStart',
'click div.oe_cart a[href^="/web?redirect"][href$="/shop/checkout"]': '_onCustomerSignin',
'click form[action="/shop/confirm_order"] a.a-submit': '_onOrder',
'click form[target="_self"] button[type=submit]': '_onOrderPayment',
'view_item_event': '_onViewItem',
'add_to_cart_event': '_onAddToCart',
},
/**
* @override
*/
start: function () {
var self = this;
// ...
const $confirmation = this.$('div.oe_website_sale_tx_status');
if ($confirmation.length) {
const orderID = $confirmation.data('order-id');
const json = $confirmation.data('order-tracking-info');
this._vpv('/stats/ecom/order_confirmed/' + orderID);
self._trackGA('event', 'purchase', json);
}
return this._super.apply(this, arguments);
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_trackGA: function () {
const websiteGA = window.gtag || function () {};
websiteGA.apply(this, arguments);
},
/**
* @private
*/
_vpv: function (page) { //virtual page view
this._trackGA('event', 'page_view', {
'page_path': page,
});
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* @private
*/
_onViewItem(event, productTrackingInfo) {
const trackingInfo = {
'currency': productTrackingInfo['currency'],
'value': productTrackingInfo['price'],
'items': [productTrackingInfo],
};
this._trackGA('event', 'view_item', trackingInfo);
},
/**
* @private
*/
_onAddToCart(event, ...productsTrackingInfo) {
const trackingInfo = {
'currency': productsTrackingInfo[0]['currency'],
'value': productsTrackingInfo.reduce((acc, val) => acc + val['price'] * val['quantity'], 0),
'items': productsTrackingInfo,
};
this._trackGA('event', 'add_to_cart', trackingInfo);
},
/**
* @private
*/
_onAddProductIntoCart: function () {
var productID = this.$('input[name="product_id"]').attr('value');
this._vpv('/stats/ecom/product_add_to_cart/' + productID);
},
/**
* @private
*/
_onCheckoutStart: function () {
this._vpv('/stats/ecom/customer_checkout');
},
/**
* @private
*/
_onCustomerSignin: function () {
this._vpv('/stats/ecom/customer_signin');
},
/**
* @private
*/
_onOrder: function () {
if ($('#top_menu [href="/web/login"]').length) {
this._vpv('/stats/ecom/customer_signup');
}
this._vpv('/stats/ecom/order_checkout');
},
/**
* @private
*/
_onOrderPayment: function () {
var method = $('#payment_method input[name=provider]:checked').nextAll('span:first').text();
this._vpv('/stats/ecom/order_payment/' + method);
},
});
return publicWidget.registry.websiteSaleTracking;
});

View file

@ -0,0 +1,144 @@
odoo.define('website_sale.utils', function (require) {
'use strict';
const wUtils = require('website.utils');
const cartHandlerMixin = {
getRedirectOption() {
const html = document.documentElement;
this.stayOnPageOption = html.dataset.add2cartRedirect === '1';
this.forceDialog = html.dataset.add2cartRedirect === '2';
},
getCartHandlerOptions(ev) {
this.isBuyNow = ev.currentTarget.classList.contains('o_we_buy_now');
const targetSelector = ev.currentTarget.dataset.animationSelector || 'img';
this.$itemImgContainer = this.$(ev.currentTarget).closest(`:has(${targetSelector})`);
},
/**
* Used to add product depending on stayOnPageOption value.
*/
addToCart(params) {
if (this.isBuyNow) {
params.express = true;
} else if (this.stayOnPageOption) {
return this._addToCartInPage(params);
}
return wUtils.sendRequest('/shop/cart/update', params);
},
/**
* @private
*/
_addToCartInPage(params) {
params.force_create = true;
return this._rpc({
route: "/shop/cart/update_json",
params: params,
}).then(async data => {
sessionStorage.setItem('website_sale_cart_quantity', data.cart_quantity);
if (data.cart_quantity && (data.cart_quantity !== parseInt($(".my_cart_quantity").text()))) {
// No animation if the product's page images are hidden
if ($('div[data-image_width]').data('image_width') !== 'none') {
await animateClone($('header .o_wsale_my_cart').first(), this.$itemImgContainer, 25, 40);
}
updateCartNavBar(data);
}
});
},
};
function animateClone($cart, $elem, offsetTop, offsetLeft) {
if (!$cart.length) {
return Promise.resolve();
}
$cart.removeClass('d-none').find('.o_animate_blink').addClass('o_red_highlight o_shadow_animation').delay(500).queue(function () {
$(this).removeClass("o_shadow_animation").dequeue();
}).delay(2000).queue(function () {
$(this).removeClass("o_red_highlight").dequeue();
});
return new Promise(function (resolve, reject) {
if(!$elem) resolve();
var $imgtodrag = $elem.find('img').eq(0);
if ($imgtodrag.length) {
var $imgclone = $imgtodrag.clone()
.offset({
top: $imgtodrag.offset().top,
left: $imgtodrag.offset().left
})
.removeClass()
.addClass('o_website_sale_animate')
.appendTo(document.body)
.css({
// Keep the same size on cloned img.
width: $imgtodrag.width(),
height: $imgtodrag.height(),
})
.animate({
top: $cart.offset().top + offsetTop,
left: $cart.offset().left + offsetLeft,
width: 75,
height: 75,
}, 1000, 'easeInOutExpo');
$imgclone.animate({
width: 0,
height: 0,
}, function () {
resolve();
$(this).detach();
});
} else {
resolve();
}
});
}
/**
* Updates both navbar cart
* @param {Object} data
*/
function updateCartNavBar(data) {
$(".my_cart_quantity")
.parents('li.o_wsale_my_cart').removeClass('d-none').end()
.addClass('o_mycart_zoom_animation').delay(300)
.queue(function () {
$(this)
.toggleClass('fa fa-warning', !data.cart_quantity)
.attr('title', data.warning)
.text(data.cart_quantity || '')
.removeClass('o_mycart_zoom_animation')
.dequeue();
});
$(".js_cart_lines").first().before(data['website_sale.cart_lines']).end().remove();
$(".js_cart_summary").replaceWith(data['website_sale.short_cart_summary']);
}
/**
* Displays `message` in an alert box at the top of the page if it's a
* non-empty string.
*
* @param {string | null} message
*/
function showWarning(message) {
if (!message) {
return;
}
var $page = $('.oe_website_sale');
var cart_alert = $page.children('#data_warning');
if (!cart_alert.length) {
cart_alert = $(
'<div class="alert alert-danger alert-dismissible" role="alert" id="data_warning">' +
'<button type="button" class="btn-close" data-bs-dismiss="alert">&times;</button> ' +
'<span></span>' +
'</div>').prependTo($page);
}
cart_alert.children('span:last-child').text(message);
}
return {
animateClone: animateClone,
updateCartNavBar: updateCartNavBar,
cartHandlerMixin: cartHandlerMixin,
showWarning: showWarning,
};
});

View file

@ -0,0 +1,51 @@
odoo.define('website_sale.validate', function (require) {
'use strict';
var publicWidget = require('web.public.widget');
var core = require('web.core');
var _t = core._t;
publicWidget.registry.websiteSaleValidate = publicWidget.Widget.extend({
selector: 'div.oe_website_sale_tx_status[data-order-id]',
/**
* @override
*/
start: function () {
var def = this._super.apply(this, arguments);
this._poll_nbr = 0;
this._paymentTransationPollStatus();
return def;
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_paymentTransationPollStatus: function () {
var self = this;
this._rpc({
route: '/shop/payment/get_status/' + parseInt(this.$el.data('order-id')),
}).then(function (result) {
self._poll_nbr += 1;
if (result.recall) {
if (self._poll_nbr < 20) {
setTimeout(function () {
self._paymentTransationPollStatus();
}, Math.ceil(self._poll_nbr / 3) * 1000);
} else {
var $message = $(result.message);
var $warning = $("<i class='fa fa-warning' style='margin-right:10px;'>");
$warning.attr("title", _t("We are waiting for confirmation from the bank or the payment provider"));
$message.find('span:first').prepend($warning);
result.message = $message.html();
}
}
self.$el.html(result.message);
});
},
});
});

View file

@ -0,0 +1,10 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
const { Component } = owl;
export class FieldVideoPreview extends Component {}
FieldVideoPreview.template = 'website_sale.FieldVideoPreview';
registry.category("fields").add("video_preview", FieldVideoPreview);

View file

@ -0,0 +1 @@
$o-wsale-products-layout-grid-ratio: 1.0 !default;

View file

@ -0,0 +1,48 @@
/* ---- OpenChatter Website ---- */
.oe_msg {
img.oe_msg_avatar {
width: 50px;
margin-right: 10px;
}
}
.oe_msg_attachment {
display: inline-block;
width: 120px;
margin: 4px 2px;
min-height: 80px;
position: relative;
border-radius: 3px;
text-align: center;
vertical-align: top;
a {
img.oe_attachment_embedded {
display: block;
position: relative;
margin: 0 0 0 10px;
width: 100px;
height: 80px;
border-radius: 1px;
border: solid 3px #FFF;
-webkit-box-shadow: 0 3px 10px rgba(0, 0, 0, 0.19);
-moz-box-shadow: 0 3px 10px rgba(0, 0, 0, 0.19);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.19);
}
div.oe_attachment_name {
display: inline-block;
max-width: 100%;
padding: 1px 3px;
margin-top: 2px;
margin-bottom: 5px;
background: #F4F5FA;
overflow: hidden;
color: #4c4c4c;
text-shadow: none;
border-radius: 3px;
word-wrap: break-word;
}
}
}

View file

@ -0,0 +1,40 @@
.o_wsale_soptions_menu_sizes {
we-title {
align-self: flex-start;
}
table {
margin: auto;
td {
margin: 0;
padding: 0;
width: 20px;
height: 20px;
border: 1px #dddddd solid;
cursor: pointer;
&.selected {
background-color: #B1D4F1;
}
}
&.oe_hover td {
&.selected {
background-color: transparent;
}
&.select {
background-color: #B1D4F1;
}
}
}
}
.o_wsale_color_preview {
width: 1em;
height: 1em;
border: 1px solid white;
display: inline-block;
vertical-align: middle;
border-radius: 50%;
}

View file

@ -0,0 +1,839 @@
// Prevent grid gutter to be higher that bootstrap gutter width to make sure
// the negative margin layout does not overflow on elements. This prevents the
// use of an ugly overflow: hidden which would break box-shadows.
$o-wsale-products-layout-grid-gutter-width: $grid-gutter-width / 2 !default;
$o-wsale-products-layout-grid-gutter-width: min($grid-gutter-width / 2, $o-wsale-products-layout-grid-gutter-width);
@mixin wsale-break-table($-list: false) {
.o_wsale_products_grid_table_wrapper {
table, tbody {
display: block;
width: 100%;
}
tr {
display: if($-list, block, flex);
width: 100%;
flex-wrap: wrap;
}
td {
display: if($-list, block, inline-block);
width: if($-list, 100%, 50%);
}
@if ($-list == false) {
[data-ppr="3"] td.oe_product {
width: 100%;
}
}
.modal-dialog {
table, tbody, tr, td {
display: revert;
}
}
@media screen and (max-width: 768px){
.oe_advanced_configurator_modal {
table, tbody, tr, td {
display: block;
width: 100%;
}
thead {
display: none;
}
}
}
}
}
@mixin o-wsale-scrollbar-subdle($-vertical: true) {
$-o-scrollbar-subdle-bg: rgba($dark, 0.05);
scrollbar-color: currentColor $-o-scrollbar-subdle-bg;
scrollbar#{if($-vertical, -width, -height)}: 2px;
&::-webkit-scrollbar {
#{if($-vertical, width, height)}: 2px;
}
&::-webkit-scrollbar-thumb {
background: currentColor;
}
&::-webkit-scrollbar-track {
background: $-o-scrollbar-subdle-bg;
}
}
.oe_website_sale {
// ==== Products list designs
.o_wsale_design_cards {
--o-wsale-card-border-width: 1px;
--o-wsale-card-border-radius: #{$card-border-radius};
--o-wsale-card-info-padding: #{map-get($spacers, 2)};
--o-wsale-card-bg: #{$card-bg};
--o-wsale-card-color: #{adjust-color-to-background($body-color, $card-bg)};
--o-wsale-card-text-muted: #{adjust-color-to-background($text-muted, $card-bg, mute-color($color-contrast-light), mute-color($color-contrast-dark))};
$-br-top: calc(#{$card-border-radius} - 1px);
--o-wsale-card-thumb-border-radius: #{$-br-top} #{$-br-top} 0 0;
}
.o_wsale_design_thumbs {
--o-wsale-card-border-width: 0;
--o-wsale-card-info-padding: #{map-get($spacers, 3)} 0;
--o-wsale-card-thumb-border-radius: #{$o-wsale-products-layout-grid-gutter-width * .5};
--o-wsale-card-thumb-shadow: 0 13px 27px -5px #{scale-color(map-get($theme-colors, 'primary'), $alpha: -90%)},
0 8px 16px -8px rgba(0, 0, 0, .28);
}
.o_wsale_design_grid {
--o-wsale-grid-border: 1px solid #{$card-border-color};
--o-wsale-card-border-width: 0;
}
// ==== Products list thumb options
.o_wsale_context_thumb_4_3 {
--o-wsale-card-thumb-aspect-ratio: 4/3;
}
.o_wsale_context_thumb_4_5 {
--o-wsale-card-thumb-aspect-ratio: 4/5;
}
.o_wsale_context_thumb_2_3 {
--o-wsale-card-thumb-aspect-ratio: 2/3;
}
.o_wsale_context_thumb_cover {
--o-wsale-card-thumb-fill-mode: cover;
}
.o_wsale_filmstip_wrapper {
scroll-snap-type: x mandatory;
@include o-wsale-scrollbar-subdle($-vertical: false);
.o_wsale_filmstip {
transform: translateX(0);
}
}
.activeDrag * {
cursor: grabbing !important;
cursor: -webkit-grabbing;
}
.o_wsale_products_grid_before_rail{
scrollbar-width: none;
-ms-overflow-style: none;
}
.o_wsale_products_grid_before_rail::-webkit-scrollbar {
width: 0;
height: 0;
}
.o_payment_form .card {
border-radius: 4px !important;
}
.address-inline address {
display: inline-block;
}
table#cart_products tr td, table#suggested_products tr td {
vertical-align: middle;
}
table#cart_products {
margin-bottom: 0;
td, th {
&:first-child {
padding-left: $grid-gutter-width*0.5;
}
}
}
h1[itemprop="name"], .td-product_name {
word-break: break-word;
word-wrap: break-word;
overflow-wrap: break-word;
}
h1[itemprop="name"] {
font-size: $h3-font-size;
font-weight: $font-weight-bold;
}
@include media-breakpoint-down(md) {
.td-img {
display: none;
}
}
.toggle_summary_div {
@include media-breakpoint-up(xl) {
max-width: 400px;
}
}
// Extra price badge
.text-bg-light .variant_price_extra.text-muted {
// Needed to be visible on a dark <body> background.
color: adjust-color-to-background($text-muted, $light, mute-color($color-contrast-light), mute-color($color-contrast-dark)) !important;
}
input.js_quantity {
min-width: 48px;
text-align: center;
}
input.quantity {
padding: 0;
}
.table-striped tbody tr td:nth-of-type(even) {
--table-accent-bg: rgba(0, 0, 0, 0.025);
}
.table-striped tbody tr td:nth-of-type(odd) {
--table-accent-bg: rgba(0, 0, 0, 0);
}
*:not(#product_attributes_simple) > .table-sm tbody td {
padding-top: 0.75rem;
padding-bottom: 0.75rem;
}
#o_wsale_offcanvas {
width: 80%;
max-width: $offcanvas-horizontal-width;
.o_searchbar_form .dropdown-menu {
right: 0;
}
#o_wsale_offcanvas_content {
@include o-wsale-scrollbar-subdle();
}
.o_wsale_offcanvas_title {
&::after, &:not(.collapsed)::after {
width: .75rem;
height: .75rem;
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='#{$headings-color}'><path transform='#{rotate(45, 8, 8)}' d='M.293.293a1 1 0 0 1 1.414 0L8 6.586 14.293.293a1 1 0 1 1 1.414 1.414L9.414 8l6.293 6.293a1 1 0 0 1-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L6.586 8 .293 1.707a1 1 0 0 1 0-1.414z'/></svg>");
background-size: .75rem;
}
&:not(.collapsed)::after {
transform: rotate(-45deg);
}
// Since this nav is visible on mobile only, remove all visual
// effects related to keyboard/mouse navigation.
&, &:focus, &:hover {
box-shadow: none;
z-index: 0;
}
}
}
#products_grid_before {
// == Guess the distance with the navbar
$-container-top-gap: calc(#{map-get($spacers, 2)} + var(--gutter-x));
// == Guess the distance with the viewport's top.
// Defined using CSS variables to ease custom-headers overrides.
--o_ws_sidebar_top_gap: calc(#{$navbar-padding-y * 2} + #{$btn-padding-y-lg * 2} + #{$-container-top-gap});
@if (o-website-value('header-scroll-effect') == null) or (o-website-value('header-scroll-effect') == 'fixed') {
top: var(--o_ws_sidebar_top_gap);
} @else {
top: $-container-top-gap;
}
.css_attribute_color {
height: 32px;
width: 32px;
}
}
.o_pricelist_dropdown, .o_sortby_dropdown {
min-width: fit-content;
}
}
#product_detail ~ .oe_structure.oe_empty > section:first-child,
.o_shop_discussion_rating {
border-top: 1px solid map-get($grays, '400');
}
.o_alternative_product {
margin: auto;
}
// Base style for a product card with image/description
.oe_product_cart {
flex-direction: var(--o-wsale-card-flex-direction, column);
border: $card-border-width solid $card-border-color;
border-width: var(--o-wsale-card-border-width, 0 0 1px);
border-radius: var(--o-wsale-card-border-radius, 0);
padding: var(--o-wsale-card-padding, 0);
background-color: var(--o-wsale-card-bg);
color: var(--o-wsale-card-color);
.oe_product_image {
min-width: var(--o-wsale-card-thumb-size);
width: var(--o-wsale-card-thumb-size);
.oe_product_image_link {
padding-top: calc(100% / (var(--o-wsale-card-thumb-aspect-ratio, 1)));
.oe_product_image_img_wrapper {
@include o-position-absolute(0, 0, 0, 0);
img {
box-shadow: var(--o-wsale-card-thumb-shadow);
border-radius: var(--o-wsale-card-thumb-border-radius);
object-fit: var(--o-wsale-card-thumb-fill-mode, contain);
}
}
}
}
.o_wsale_product_information {
padding: var(--o-wsale-card-info-padding, #{map-get($spacers, 2)} 0);
}
.oe_subdescription div {
min-height: calc(#{$font-size-sm * 1.1} * 2);
overflow: hidden;
display: -webkit-box;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
line-height: 1.1;
}
// Needed to be visible on a dark <body> background. The rule is defined on
// the <div> child to avoid overriding the default "text-muted" color if the
// "--o-wsale-card-text-muted" variable is not defined.
.oe_subdescription.text-muted > div {
color: var(--o-wsale-card-text-muted) !important;
}
.o_wsale_product_btn:empty {
display: none !important;
}
.o_ribbon_left, .o_ribbon_right {
box-shadow: 0px -10px 70px 30px rgba(black, 0.05);
}
.o_product_link {
@include o-position-absolute(0, 0, 0, 0);
z-index: 1;
}
}
// ==== THE GRID
#products_grid {
.o_wsale_products_grid_table_wrapper .table {
table-layout: fixed;
td {
padding: $o-wsale-products-layout-grid-gutter-width * .5;
border: var(--o-wsale-grid-border, 0);
@if $o-wsale-products-layout-grid-gutter-width <= 0 {
border: $card-border-width solid $card-border-color;
}
}
}
.o_wsale_products_grid_table_wrapper {
// Necessary to compensate the outer border-spacing of the table. No
// overflow will occur as the gutter width cannot be higher than the
// BS4 grid gutter and the vertical margins of the wrapper's parent are
// set accordingly.
// Note: a possible layout could also be ok by removing the wrapper
// related spacings and setting a background to it, thus including the
// outer border spacing as part of the design.
margin: (-$o-wsale-products-layout-grid-gutter-width / 2);
}
}
#products_grid:not(.o_wsale_layout_list) {
@include media-breakpoint-down(lg) {
@include wsale-break-table();
}
@include media-breakpoint-up(lg) {
td.oe_product {
padding-bottom: $o-wsale-products-layout-grid-gutter-width * .5;
.o_wsale_product_btn {
@include o-position-absolute(auto, auto, calc(100% + #{map-get($spacers, 2)}), map-get($spacers, 2));
z-index: 2;
}
&:not(:hover) .o_wsale_product_btn {
opacity: 0;
}
}
// Sliglty increase padding for larger blocks
@for $x from 2 through 4 {
[class*="o_wsale_product_grid_wrapper_#{$x}_"] {
--o-wsale-card-info-padding: #{map-get($spacers, 3)} 0 #{map-get($spacers, 2)};
}
.o_wsale_design_cards [class*="o_wsale_product_grid_wrapper_#{$x}_"] {
--o-wsale-card-info-padding: #{map-get($spacers, 3)} #{map-get($spacers, 3)} #{map-get($spacers, 2)};
}
}
}
}
#products_grid.o_wsale_layout_list {
@include wsale-break-table($-list: true);
.oe_product {
--o-wsale-card-border-radius: 0;
--o-wsale-card-flex-direction: row;
--o-wsale-card-info-padding: 0 #{map-get($spacers, 3)};
// The more an image is portait, the more its size decreases
--o-wsale-card-thumb-size: calc(100px * var(--o-wsale-card-thumb-aspect-ratio, 1));
@include media-breakpoint-up(lg) {
--o-wsale-card-thumb-size: calc(160px * var(--o-wsale-card-thumb-aspect-ratio, 1));
h6, .h6 {
font-size: $font-size-base * 1.3;
}
}
}
.oe_product {
--o-wsale-card-padding: #{0 0 $o-wsale-products-layout-grid-gutter-width};
}
.o_wsale_design_cards .oe_product {
--o-wsale-card-padding: #{$o-wsale-products-layout-grid-gutter-width * .5};
--o-wsale-card-info-padding: #{map-get($spacers, 2)} #{map-get($spacers, 3)};
}
}
.o_wsale_products_main_row {
// Special case. Normally vertical margins would be set using the BS4
// mt-* / my-* / mb-* utility classes, but here we need to use the shop max
// grid gutter width to prevent the grid wrapper to overflow because of its
// negative margins.
margin-bottom: $grid-gutter-width / 2;
margin-top: $grid-gutter-width / 2;
}
.oe_cart {
table td:first-child {
min-width: 76px;
}
> .oe_structure {
clear: both;
}
}
div#payment_method {
div.list-group {
margin-left: 40px;
}
.list-group-item {
padding-top: 5px;
padding-bottom: 5px;
}
}
ul.wizard {
padding: 0;
margin-top: 20px;
list-style: none outside none;
border-radius: 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.065);
li {
border: 1px solid map-get($grays, '200');
border-right-width: 0;
position: relative;
float: left;
padding: 0 10px 0 20px;
margin: 0;
line-height: 38px;
background: #fbfbfb;
.chevron {
position: absolute;
top: 0;
right: -10px;
z-index: 1;
display: block;
border: 20px solid transparent;
border-right: 0;
border-left: 10px solid map-get($grays, '200');
}
.chevron:before {
position: absolute;
top: -20px;
right: 1px;
display: block;
border: 20px solid transparent;
border-right: 0;
border-left: 10px solid #fbfbfb;
content: "";
}
.o_link_disable {
text-decoration: none;
color: inherit;
cursor: text;
}
&.text-success {
background: #f3f4f5;
}
&.text-success .chevron:before {
border-left: 10px solid #f5f5f5;
}
&.text-primary {
background: #f1f6fc;
}
&.text-primary .chevron:before {
border-left: 10px solid #f1f6fc;
}
&:first-child {
padding-left: 15px;
border-radius: 4px 0 0 4px;
}
&:last-child {
border-radius: 0 4px 4px 0;
border-right-width: 1px;
.chevron {
display: none;
}
}
}
}
.mycart-popover {
max-width: 500px;
min-width: 250px;
.cart_line {
border-bottom: 1px #EEE solid;
}
.popover-body {
max-height: 70vh;
overflow-y: auto;
}
}
tr#empty {
display: none;
}
.js_change_shipping {
cursor: pointer;
}
a.no-decoration {
cursor: pointer;
text-decoration: none !important;
}
#o-carousel-product {
transition: top 200ms;
&.css_not_available {
opacity: 0.2;
}
.carousel-outer {
height: 400px;
max-height: 90vh;
.carousel-inner {
img {
object-fit: contain;
}
}
}
.carousel-control-prev .fa {
padding-right: 2px;
}
.carousel-control-next .fa {
padding-left: 2px;
}
.carousel-control-prev, .carousel-control-next {
height: 70%;
top: 15%;
opacity: 0.5;
cursor: pointer;
transition: opacity 0.8s;
&:focus {
opacity: 0.65;
}
&:hover {
opacity: 0.8;
}
> span {
width: 2.5rem;
height: 2.5rem;
line-height: 2.5rem;
color: map-get($grays, '900');
background: white;
font-size: 1.15rem;
border: 1px solid map-get($grays, '400');
border-radius: 50%;
}
@include media-breakpoint-down(lg) {
> span {
width: 2rem;
height: 2rem;
line-height: 2rem;
font-size: 1rem;
}
}
}
@include media-breakpoint-up(xl) {
&:not(:hover) {
.carousel-control-prev, .carousel-control-next {
opacity: 0;
}
}
}
.carousel-item, .o_carousel_product_indicators {
transition: transform 0.2s ease-out;
}
.carousel-indicators {
transition: transform 0.3s ease-in-out;
}
.o_carousel_product_indicators {
max-height: 400px;
@include media-breakpoint-up(lg) {
.carousel-indicators {
justify-content: start;
li {
width: 64px;
height: 64px;
text-indent: unset;
transition: none;
border: 1px solid map-get($grays, '400');
.o_product_video_thumb {
@include o-position-absolute(0, 0, 0, 0);
line-height: 64px;
}
&.active {
border-color: map-get($theme-colors, 'primary');
}
&:hover {
opacity: 1;
}
}
}
}
}
@include media-breakpoint-down(lg) {
&.o_carousel_product_left_indicators {
flex-direction: column-reverse;
}
.carousel-indicators {
justify-content: center;
li {
width: 8px;
height: 8px;
min-width: 8px;
border-radius: 50%;
border: 2px solid map-get($grays, '400');
&.active {
border-color: map-get($theme-colors, 'primary');
background-color: map-get($theme-colors, 'primary');
}
> div {
display: none;
}
}
}
}
@include media-breakpoint-up(lg) {
.carousel-indicators li {
margin: 0;
&:not(:first-child) {
margin-left: 10px;
}
}
&.o_carousel_product_left_indicators {
.carousel-outer {
height: 500px;
}
.o_carousel_product_indicators {
max-height: 500px;
}
.carousel-indicators li {
margin: 0;
&:not(:first-child) {
margin-top: 10px;
}
}
}
}
}
.ecom-zoomable {
&[data-ecom-zoom-click] {
img.product_detail_img {
cursor: zoom-in;
}
}
img[data-zoom] {
cursor: zoom-in;
}
.o_editable img[data-zoom] {
cursor: pointer;
}
.zoomodoo-flyout {
box-shadow: 0 0 20px 2px rgba(black, 0.2);
z-index: 1050;
}
}
#coupon_box form {
max-width: 300px;
}
.o_website_sale_animate {
opacity: 0.7;
position: absolute !important;
height: 150px;
width: 150px;
z-index: 1020;
}
.o_red_highlight {
background: map-get($theme-colors, 'danger') !important;
box-shadow: 0 0 0 0 rgba(240,8,0,0.4);
transition: all 0.5s linear;
}
.o_shadow_animation {
box-shadow: 0 0 5px 10px rgba(240,8,0,0.4)!important;
}
.o_mycart_zoom_animation {
transform: scale(1.4);
transition: all 0.3s ease-in-out;
}
/* product recently viewed snippet */
.o_carousel_product_card {
.o_carousel_product_card_img_top {
object-fit: scale-down;
@include media-breakpoint-down(md) {
height: 12rem;
}
@include media-breakpoint-up(md) {
height: 8rem;
}
@include media-breakpoint-up(lg) {
height: 12rem;
}
}
.o_carousel_product_img_link:hover + .o_carousel_product_remove {
display: block;
}
}
.o_carousel_product_card_wrap {
@include media-breakpoint-up(sm) {
float: left;
}
}
.o_carousel_product_control {
top: percentage(1/3);
bottom: percentage(1/3);
width: 2rem;
border-radius: 5px;
background-color: $o-enterprise-primary-color;
}
.o_carousel_product_remove {
position: absolute;
display: none;
cursor: pointer;
right: 5%;
top: 5%;
}
.o_carousel_product_remove:hover {
display: block;
}
// customer reviews
.o_product_page_reviews_title {
.fa {
font-size: 1.4rem;
color: map-get($theme-colors, 'primary');
&:before {
content: "\f067";
}
}
&:not(.collapsed) {
.fa:before {
content: "\f068";
}
}
}
#o_product_page_reviews_content {
.o_website_rating_avg {
h1 {
font-size: 3.5rem;
}
}
.o_portal_chatter_composer {
position: sticky;
transition: top 200ms;
}
.o_portal_chatter_messages > .o_portal_chatter_message {
border-bottom: 1px solid rgba(0, 0, 0, 0.1);;
padding: 1rem 0;
margin-bottom: 1rem;
}
.o_portal_chatter_avatar {
border-radius: 50%;
}
}

View file

@ -0,0 +1,67 @@
.o_website_sale_image_list .o_kanban_view.o_kanban_ungrouped {
width: auto;
.o_kanban_record{
flex: 0 1 50%;
position: relative;
@include media-breakpoint-up(md) {
flex: 0 0 percentage(1/3);
}
@include media-breakpoint-up(lg) {
flex: 0 0 percentage(1/5);
}
@include media-breakpoint-up(xl) {
flex: 0 0 percentage(1/6);
}
// make the image square and in the center
.o_squared_image {
position: relative;
overflow: hidden;
padding-bottom: 100%;
> img {
position: absolute;
margin: auto;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
}
.o_product_image_size {
position: absolute;
top: 0;
left: 0;
}
}
}
.o_website_sale_image_modal {
.o_website_sale_image_modal_container {
border-left: 1px solid map-get($grays, '400');
.o_field_image {
margin-bottom: 0;
box-shadow: 0 2px 10px map-get($grays, '300');
> img {
border: 1px solid map-get($grays, '400');
height: 200px;
width: auto;
}
}
}
.o_video_container {
height: 200px;
position: relative;
@include o-we-preview-box($text-muted);
.o_invalid_warning {
width: 90%;
@include o-position-absolute($top: 50%, $left: 50%);
transform: translate(-50%, -50%);
}
}
}

View file

@ -0,0 +1,81 @@
.o_dashboard_sales {
h2 {
padding: 15px;
}
h4 { margin: 3px 0 4px 0 !important; }
.o_demo_background {
margin-top: 16px;
height: 350px;
background-size: 100% !important;
background: url("/website_sale/static/src/img/website_sale_dashboard_sales_demo.png") no-repeat;
position: relative;
opacity: 0.2;
}
.o_demo_message {
color: $o-main-color-muted;
width: 100%;
@include o-position-absolute($left: 0, $top: 12%);
display: flex;
justify-content: center;
}
.o_link_enable {
@include media-breakpoint-up(lg) {
border-right: 1px solid #ccc;
}
height: initial;
padding: 5px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
a {
color: darken($o-brand-lightsecondary, 40%);
}
&:hover {
background-color: $o-brand-lightsecondary;
}
.o_highlight {
color: $o-brand-primary;
font-size: 20px;
font-weight: bold;
}
}
.o_link_disable {
@include media-breakpoint-up(lg) {
border-right: 1px solid #ccc;
}
height: initial;
padding: 5px;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
margin: 10px 0px;
color: darken($o-brand-lightsecondary, 40%);
.o_highlight {
font-size: 20px;
font-weight: bold;
}
}
.o_invisible_border {
border-right: 0px;
}
.o_top_margin {
margin-top: 20px;
}
.o_graph_canvas_container {
position: relative;
top: 0px;
left: 0px;
width: 100%;
height: 30em;
}
.o_dashboard_utms {
.utm_chart_image {
display: block;
margin: auto;
}
.utm_dropdown {
margin-top: -5px;
}
}
}

View file

@ -0,0 +1,199 @@
//## Website Sale frontent design
//## ----------------------------
// Theming variables
$o-wsale-wizard-thickness: 0.125rem;
$o-wsale-wizard-dot-size: 0.625rem;
$o-wsale-wizard-dot-active-glow: 0.25rem;
$o-wsale-wizard-color-inner: white;
$o-wsale-wizard-color-default: map-get($grays, '200');
$o-wsale-wizard-dot-active: map-get($theme-colors, 'primary');
$o-wsale-wizard-dot-completed: map-get($theme-colors, 'success');
$o-wsale-wizard-label-default: $text-muted;
$o-wsale-wizard-label-active: $body-color;
$o-wsale-wizard-label-completed: $success;
.progress-wizard {
// Scoped variables
$tmp-dot-radius: ($o-wsale-wizard-dot-size + $o-wsale-wizard-thickness)*0.5;
$tmp-check-size: max($font-size-base, $o-wsale-wizard-dot-size + $o-wsale-wizard-thickness + $o-wsale-wizard-dot-active-glow*2);
$tmp-check-pos: $o-wsale-wizard-dot-size*0.5 - $tmp-check-size*0.5;
margin-top: $grid-gutter-width*0.5;
padding: 0 $grid-gutter-width*0.5;
@include media-breakpoint-up(md) {
padding: 0;
}
.progress-wizard-step {
position: relative;
@include media-breakpoint-up(md) {
margin-top: $tmp-dot-radius + $o-wsale-wizard-thickness*3.5;
float: left;
width: percentage(1/3);
.o_wizard_has_extra_step + & {
width: percentage(1/4);
}
}
@include media-breakpoint-down(md) {
&.disabled, &.complete {
display:none;
}
}
.progress-wizard-dot {
width: $o-wsale-wizard-dot-size;
height: $o-wsale-wizard-dot-size;
position: relative;
display: inline-block;
background-color: $o-wsale-wizard-color-inner;
border-radius: 50%;
box-shadow: 0 0 0 $o-wsale-wizard-thickness $o-wsale-wizard-color-default;
@include media-breakpoint-up(md) {
@include o-position-absolute($left: 50%);
margin: (-$tmp-dot-radius) 0 0 (-$o-wsale-wizard-dot-size*0.5);
}
}
.progress-wizard-steplabel {
color: $o-wsale-wizard-label-default;
margin: 5px 0 5px 5px;
font-size: $font-size-base;
display: inline-block;
@include media-breakpoint-up(md) {
display: block;
margin: (0.625rem + $tmp-dot-radius) 0 20px 0;
}
@include media-breakpoint-down(md) {
margin-left: -15px;
font-size: 24px;
}
}
.progress-wizard-bar {
height: $o-wsale-wizard-thickness;
background-color: $o-wsale-wizard-color-default;
}
&.active {
.progress-wizard-dot {
animation: fadeIn 1s ease 0s 1 normal none running;
background: $o-wsale-wizard-dot-active;
box-shadow: 0 0 0 ($o-wsale-wizard-dot-active-glow - 0.0625rem) $o-wsale-wizard-color-inner,
0 0 0 $o-wsale-wizard-dot-active-glow rgba($o-wsale-wizard-dot-active, 0.5);
}
.progress-wizard-steplabel {
color: $o-wsale-wizard-label-active;
font-weight: bolder;
}
}
&.complete {
.progress-wizard-dot {
background: none;
box-shadow: none;
&:after {
@include o-position-absolute($tmp-check-pos, $left: $tmp-check-pos);
width: $tmp-check-size;
height: $tmp-check-size;
border-radius: 100%;
background: $o-wsale-wizard-color-inner;
color: $o-wsale-wizard-dot-completed;
text-align: center;
line-height: 1;
font-size: $tmp-check-size;
font-family: FontAwesome;
content: "\f058";
}
}
.progress-wizard-steplabel {
color: $o-wsale-wizard-label-completed;
}
&:hover:not(.disabled) {
.progress-wizard-dot:after {
color: $o-wsale-wizard-label-completed;
}
.progress-wizard-steplabel {
color: $o-wsale-wizard-label-active;
}
}
}
&.disabled {
cursor: default;
}
}
}
.o_wsale_image_viewer {
z-index: -1;
.o_wsale_image_viewer_header {
height: 40px;
z-index: 1;
}
.o_wsale_image_viewer_image {
.o_wsale_image_viewer_void {
padding-top: 64px;
padding-bottom: 156px;
}
img {
cursor: zoom-in;
}
}
.o_wsale_image_viewer_carousel {
z-index:1;
ol {
list-style: none;
.o_wsale_image_viewer_thumbnail {
width: 128px;
height: 128px;
}
li img{
border: 1px solid #CED4DA;
&.active {
border-color: #35979c;
}
}
}
}
.o_wsale_image_viewer_control {
z-index: 1;
width: 40px;
height: 40px;
&:hover {
background-color: rgba($gray-400, 0.1);
}
&.o_wsale_image_viewer_previous {
margin: 1px 1px 0 0; // not correctly centered for some reasons
}
&.o_wsale_image_viewer_next {
margin: 1px 0 0 1px; // not correctly centered for some reasons
}
}
}

View file

@ -0,0 +1,49 @@
/** @odoo-module **/
import publicWidget from 'web.public.widget';
import { cartHandlerMixin } from 'website_sale.utils';
import { WebsiteSale } from 'website_sale.website_sale';
import { _t } from 'web.core';
publicWidget.registry.AddToCartSnippet = WebsiteSale.extend(cartHandlerMixin, {
selector: '.s_add_to_cart_btn',
events: {
'click': '_onClickAddToCartButton',
},
_onClickAddToCartButton: async function (ev) {
const dataset = ev.currentTarget.dataset;
const visitorChoice = dataset.visitorChoice === 'true';
const action = dataset.action;
const productId = parseInt(dataset.productVariantId);
if (!productId) {
return;
}
if (visitorChoice) {
this._handleAdd($(ev.currentTarget.closest('div')));
} else {
const isAddToCartAllowed = await this._rpc({
route: `/shop/product/is_add_to_cart_allowed`,
params: {
product_id: productId,
},
});
if (!isAddToCartAllowed) {
this.displayNotification({
title: 'User Error',
message: _t('This product does not exist therefore it cannot be added to cart.'),
type: 'warning'
});
return;
}
this.isBuyNow = action === 'buy_now';
this.stayOnPageOption = !this.isBuyNow;
this.addToCart({product_id: productId, add_qty: 1});
}
},
});
export default publicWidget.registry.AddToCartSnippet;

View file

@ -0,0 +1,259 @@
/** @odoo-module **/
import options from 'web_editor.snippets.options';
import { _t } from 'web.core';
const Many2oneUserValueWidget = options.userValueWidgetsRegistry['we-many2one'];
const Many2oneDefaultMessageWidget = Many2oneUserValueWidget.extend({
// defaultMessage: default message to display when no records are selected
configAttributes: [...Many2oneUserValueWidget.prototype.configAttributes, 'defaultMessage'],
/**
* @override
*/
async setValue(value, methodName) {
await this._super(...arguments);
if (value === '') {
this.menuTogglerEl.textContent = this.options.defaultMessage;
}
},
});
options.userValueWidgetsRegistry['we-many2one-default-message'] = Many2oneDefaultMessageWidget;
options.registry.AddToCart = options.Class.extend({
events: _.extend({}, options.Class.prototype.events || {}, {
'click .reset-variant-picker': '_onClickResetVariantPicker',
'click .reset-product-picker': '_onClickResetProductPicker',
}),
async updateUI() {
if (this.rerender) {
this.rerender = false;
await this._rerenderXML();
return;
}
return this._super.apply(this, arguments);
},
_setButtonDisabled: function (isDisabled) {
const buttonEl = this._buttonEl();
if (isDisabled) {
buttonEl.classList.add('disabled');
} else {
buttonEl.classList.remove('disabled');
}
},
async setProductTemplate(previewMode, widgetValue, params) {
this.$target[0].dataset.productTemplate = widgetValue;
this._resetVariantChoice();
this._resetAction();
this._setButtonDisabled(false);
await this._fetchVariants(widgetValue);
this.rerender = true;
this._updateButton();
},
setProductVariant(previewMode, widgetValue, params) {
this.$target[0].dataset.productVariant = widgetValue;
this._updateButton();
},
setAction(previewMode, widgetValue, params) {
this.$target[0].dataset.action = widgetValue;
this._updateButton();
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
_onClickResetVariantPicker() {
this._resetVariantChoice();
this._resetAction();
this._updateButton();
},
_onClickResetProductPicker() {
this._resetProductChoice();
this._resetVariantChoice();
this._resetAction();
this._updateButton();
},
/**
* Fetches the variants ids from the server
*/
async _fetchVariants(productTemplateId) {
const response = await this._rpc({
model: 'product.product',
method: 'search_read',
domain: [
["product_tmpl_id", "=", parseInt(productTemplateId)],
],
fields: ['id'],
});
this.$target[0].dataset.variants = response.map(variant => variant.id);
},
_resetProductChoice() {
this.$target[0].dataset.productTemplate = '';
this._buttonEl().classList.add('disabled');
},
_resetVariantChoice() {
this.$target[0].dataset.productVariant = '';
},
_resetAction: function () {
this.$target[0].dataset.action = "add_to_cart";
},
/**
* Returns an array of variant ids from the dom
*/
_variantIds() {
return this.$target[0].dataset.variants.split(',').map(stringId => parseInt(stringId));
},
_buttonEl() {
const buttonEl = this.$target[0].querySelector('.s_add_to_cart_btn');
// In case the button was deleted somehow, we rebuild it.
if (!buttonEl) {
return this._buildButtonEl();
}
return buttonEl;
},
_buildButtonEl() {
const buttonEl = document.createElement('button');
buttonEl.classList.add("s_add_to_cart_btn", "btn", "btn-secondary", "mb-2");
this.$target[0].append(buttonEl);
return buttonEl;
},
/**
* Updates the button's html
*/
_updateButton() {
const variantIds = this._variantIds();
const buttonEl = this._buttonEl();
let productVariantId = variantIds[0];
buttonEl.dataset.visitorChoice = false;
if (variantIds.length > 1) {
// If there is more than 1 variant, that means that there are variants for the product template
// and we check if there is one selected and assign it. If not, visitorChoice is set to true
if (this.$target[0].dataset.productVariant) {
productVariantId = this.$target[0].dataset.productVariant;
} else {
buttonEl.dataset.visitorChoice = true;
}
}
buttonEl.dataset.productVariantId = productVariantId;
buttonEl.dataset.action = this.$target[0].dataset.action;
this._updateButtonContent();
this._createHiddenFormInput(productVariantId);
},
_updateButtonContent() {
let iconEl = document.createElement('i');
const buttonContent = {
add_to_cart: {classList: "fa fa-cart-plus me-2", text: _t("Add to Cart")},
buy_now: {classList: "fa fa-credit-card me-2", text: _t("Buy now")},
};
let buttonContentElement = buttonContent[this.$target[0].dataset.action];
iconEl.classList = buttonContentElement.classList;
this._buttonEl().replaceChildren(iconEl, buttonContentElement.text);
},
/**
* Because sale_product_configurator._handleAdd() requires a hidden input to retrieve the productId,
* this method creates a hidden input in the form of the button to make the modal behaviour possible.
*/
_createHiddenFormInput(productVariantId) {
const inputEl = this._buttonEl().querySelector('input[type="hidden"][name="product_id"]');
if (inputEl) {
// If the input already exists, we change its value
inputEl.setAttribute('value', productVariantId);
} else {
// Otherwise, we create the input element
let inputEl = document.createElement('input');
inputEl.setAttribute('type', 'hidden');
inputEl.setAttribute('name', 'product_id');
inputEl.setAttribute('value', productVariantId);
this._buttonEl().append(inputEl);
}
},
/**
* Called when the template is chosen and that we want to update the m2o variant widget with the right variants.
*/
async _renderCustomXML(uiFragment) {
if (this.$target[0].dataset.productTemplate) {
// That means that a template was selected and we want to update the content of the variant picker based on the template id
const productVariantPickerEl = uiFragment.querySelector('we-many2one-default-message[data-name="product_variant_picker_opt"]');
productVariantPickerEl.dataset.domain = `[["product_tmpl_id", "=", ${this.$target[0].dataset.productTemplate}]]`;
}
},
/**
* @override
*/
_computeWidgetState(methodName, params) {
switch (methodName) {
case 'setProductTemplate': {
return this.$target[0].dataset.productTemplate || '';
}
case 'setProductVariant': {
return this.$target[0].dataset.productVariant || '';
}
case 'setAction': {
return this.$target[0].dataset.action;
}
}
return this._super(...arguments);
},
/**
* @override
*/
async _computeWidgetVisibility(widgetName, params) {
switch (widgetName) {
case 'product_variant_picker_opt': {
return this.$target[0].dataset.productTemplate && this._variantIds().length > 1;
}
case 'product_variant_reset_opt': {
return this.$target[0].dataset.productVariant;
}
case 'product_template_reset_opt': {
return this.$target[0].dataset.productTemplate;
}
case 'action_picker_opt': {
if (this.$target[0].dataset.productTemplate) {
if (this._variantIds().length > 1) {
return this.$target[0].dataset.productVariant;
}
return true;
}
return false;
}
}
return this._super(...arguments);
},
});
export default {
AddToCart: options.registry.AddToCart,
};

View file

@ -0,0 +1,177 @@
odoo.define('website_sale.s_dynamic_snippet_products', function (require) {
'use strict';
const publicWidget = require('web.public.widget');
const DynamicSnippetCarousel = require('website.s_dynamic_snippet_carousel');
var wSaleUtils = require('website_sale.utils');
const DynamicSnippetProducts = DynamicSnippetCarousel.extend({
selector: '.s_dynamic_snippet_products',
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* Gets the category search domain
*
* @private
*/
_getCategorySearchDomain() {
const searchDomain = [];
let productCategoryId = this.$el.get(0).dataset.productCategoryId;
if (productCategoryId && productCategoryId !== 'all') {
if (productCategoryId === 'current') {
productCategoryId = undefined;
const productCategoryField = $("#product_details").find(".product_category_id");
if (productCategoryField && productCategoryField.length) {
productCategoryId = parseInt(productCategoryField[0].value);
}
if (!productCategoryId) {
this.trigger_up('main_object_request', {
callback: function (value) {
if (value.model === "product.public.category") {
productCategoryId = value.id;
}
},
});
}
if (!productCategoryId) {
// Try with categories from product, unfortunately the category hierarchy is not matched with this approach
const productTemplateId = $("#product_details").find(".product_template_id");
if (productTemplateId && productTemplateId.length) {
searchDomain.push(['public_categ_ids.product_tmpl_ids', '=', parseInt(productTemplateId[0].value)]);
}
}
}
if (productCategoryId) {
searchDomain.push(['public_categ_ids', 'child_of', parseInt(productCategoryId)]);
}
}
return searchDomain;
},
/**
* Gets the tag search domain
*
* @private
*/
_getTagSearchDomain() {
const searchDomain = [];
let productTagIds = this.$el.get(0).dataset.productTagIds;
productTagIds = productTagIds ? JSON.parse(productTagIds) : [];
if (productTagIds.length) {
searchDomain.push(['all_product_tag_ids', 'in', productTagIds.map(productTag => productTag.id)]);
}
return searchDomain;
},
/**
* Method to be overridden in child components in order to provide a search
* domain if needed.
* @override
* @private
*/
_getSearchDomain: function () {
const searchDomain = this._super.apply(this, arguments);
searchDomain.push(...this._getCategorySearchDomain());
searchDomain.push(...this._getTagSearchDomain());
const productNames = this.$el.get(0).dataset.productNames;
if (productNames) {
const nameDomain = [];
for (const productName of productNames.split(',')) {
// Ignore empty names
if (!productName.length) {
continue;
}
// Search on name, internal reference and barcode.
if (nameDomain.length) {
nameDomain.unshift('|');
}
nameDomain.push(...[
'|', '|', ['name', 'ilike', productName],
['default_code', '=', productName],
['barcode', '=', productName],
]);
}
searchDomain.push(...nameDomain);
}
return searchDomain;
},
/**
* @override
*/
_getRpcParameters: function () {
const productTemplateId = $("#product_details").find(".product_template_id");
return Object.assign(this._super.apply(this, arguments), {
productTemplateId: productTemplateId && productTemplateId.length ? productTemplateId[0].value : undefined,
});
},
});
const DynamicSnippetProductsCard = publicWidget.Widget.extend({
selector: '.o_carousel_product_card',
read_events: {
'click .js_add_cart': '_onClickAddToCart',
'click .js_remove': '_onRemoveFromRecentlyViewed',
},
init(root, options) {
const parent = options.parent || root;
this._super(parent, options);
},
start() {
this.add2cartRerender = this.el.dataset.add2cartRerender === 'True';
},
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
/**
* Event triggered by a click on the Add to cart button
*
* @param {OdooEvent} ev
*/
async _onClickAddToCart(ev) {
const $card = $(ev.currentTarget).closest('.card');
const data = await this._rpc({
route: "/shop/cart/update_json",
params: {
product_id: $card.find('input[data-product-id]').data('product-id'),
add_qty: 1
},
});
const $navButton = $('header .o_wsale_my_cart').first();
await wSaleUtils.animateClone($navButton, $(ev.currentTarget).parents('.card'), 25, 40);
wSaleUtils.updateCartNavBar(data);
if (this.add2cartRerender) {
this.trigger_up('widgets_start_request', {
$target: this.$el.closest('.s_dynamic'),
});
}
},
/**
* Event triggered by a click on the remove button on a "recently viewed"
* template.
*
* @param {OdooEvent} ev
*/
async _onRemoveFromRecentlyViewed(ev) {
const $card = $(ev.currentTarget).closest('.card');
await this._rpc({
route: "/shop/products/recently_viewed_delete",
params: {
product_id: $card.find('input[data-product-id]').data('product-id'),
},
});
this.trigger_up('widgets_start_request', {
$target: this.$el.closest('.s_dynamic'),
});
},
});
publicWidget.registry.dynamic_snippet_products_cta = DynamicSnippetProductsCard;
publicWidget.registry.dynamic_snippet_products = DynamicSnippetProducts;
return DynamicSnippetProducts;
});

View file

@ -0,0 +1,90 @@
// class name are dynamically added.
// If you don't find it with a grep, don't consider it as useless without extra check.
.s_product_product_centered {
.card {
overflow: visible;
margin-top: 6rem;
padding-top: 6rem;
}
.o_carousel_product_img_link {
max-width: 75%;
margin-top: -12rem;
left: 0;
right: 0;
}
}
.s_product_product_banner {
img {
max-height: 400px;
}
}
.s_product_product_horizontal_card img {
img {
height: 100%;
}
}
@include media-breakpoint-down(lg) {
.s_product_product_horizontal_card img {
height: 12rem;
}
}
.o_dynamic_product_hovered {
img {
transition: transform 250ms ease;
}
&:hover img {
transform: scale(1.15);
}
}
.o_carousel_multiple_rows {
.row {
padding: 1rem;
}
}
// Cover image
img.o_img_product_cover {
object-fit: cover;
}
// Force the image to have a square ratio
img.o_img_product_square {
aspect-ratio: 1 / 1;
}
.o_card_group {
> .row {
padding-left: $grid-gutter-width / 2;
padding-right: $grid-gutter-width / 2;
> div {
padding: 0 !important;
&:first-child > .o_carousel_product_card{
border-left: $card-border-width solid $card-border-color !important;
}
}
&:first-child {
.o_carousel_product_card {
border-top: $card-border-width solid $card-border-color !important;
}
> div:first-child .o_carousel_product_card {
@include border-top-start-radius($border-radius-lg !important);
}
> div:last-child .o_carousel_product_card {
@include border-top-end-radius($border-radius-lg !important);
}
}
&:last-child {
> div:first-child .o_carousel_product_card {
@include border-bottom-start-radius($border-radius-lg !important);
}
> div:last-child .o_carousel_product_card {
@include border-bottom-end-radius($border-radius-lg !important);
}
}
}
}

View file

@ -0,0 +1,105 @@
odoo.define('website_sale.s_dynamic_snippet_products_options', function (require) {
'use strict';
const options = require('web_editor.snippets.options');
const s_dynamic_snippet_carousel_options = require('website.s_dynamic_snippet_carousel_options');
var wUtils = require('website.utils');
const alternativeSnippetRemovedOptions = [
'filter_opt', 'product_category_opt', 'product_tag_opt', 'product_names_opt',
]
const dynamicSnippetProductsOptions = s_dynamic_snippet_carousel_options.extend({
/**
*
* @override
*/
init: function () {
this._super.apply(this, arguments);
this.modelNameFilter = 'product.product';
// Directly calling $() will not work in this case since we are querying something
// in an iframe
const productTemplateId = this.$target.closest("#wrapwrap").find("input.product_template_id");
this.hasProductTemplateId = productTemplateId.val();
if (!this.hasProductTemplateId) {
this.contextualFilterDomain.push(['product_cross_selling', '=', false]);
}
this.productCategories = {};
this.isAlternativeProductSnippet = this.$target.hasClass('o_wsale_alternative_products');
},
/**
* @override
*/
onBuilt() {
this._super.apply(this, arguments);
// TODO Remove in master.
this.$target[0].dataset['snippet'] = 's_dynamic_snippet_products';
},
//--------------------------------------------------------------------------
// Private
//--------------------------------------------------------------------------
/**
* @private
* @override
*/
_computeWidgetVisibility(widgetName, params) {
if (this.isAlternativeProductSnippet && alternativeSnippetRemovedOptions.includes(widgetName)) {
return false;
}
return this._super(...arguments);
},
/**
* Fetches product categories.
* @private
* @returns {Promise}
*/
_fetchProductCategories: function () {
return this._rpc({
model: 'product.public.category',
method: 'search_read',
kwargs: {
domain: wUtils.websiteDomain(this),
fields: ['id', 'name'],
}
});
},
/**
*
* @override
* @private
*/
_renderCustomXML: async function (uiFragment) {
await this._super.apply(this, arguments);
await this._renderProductCategorySelector(uiFragment);
},
/**
* Renders the product categories option selector content into the provided uiFragment.
* @private
* @param {HTMLElement} uiFragment
*/
_renderProductCategorySelector: async function (uiFragment) {
const productCategories = await this._fetchProductCategories();
for (let index in productCategories) {
this.productCategories[productCategories[index].id] = productCategories[index];
}
const productCategoriesSelectorEl = uiFragment.querySelector('[data-name="product_category_opt"]');
return this._renderSelectUserValueWidgetButtons(productCategoriesSelectorEl, this.productCategories);
},
/**
* @override
* @private
*/
_setOptionsDefaultValues: function () {
this._setOptionValue('productCategoryId', 'all');
this._super.apply(this, arguments);
},
});
options.registry.dynamic_snippet_products = dynamicSnippetProductsOptions;
return dynamicSnippetProductsOptions;
});

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="website_sale.FieldVideoPreview" owl="1">
<div class="ratio ratio-16x9 mt-2" t-if="props.value">
<t t-out="props.value"/>
</div>
</t>
</templates>

View file

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-extend="website.dashboard_header">
<t t-jquery="div.o_dashboard_common" t-operation="append">
<div class="col-12 o_box" t-if="widget.dashboards_data.sales.summary.order_unpaid_count || widget.dashboards_data.sales.summary.order_to_invoice_count || widget.dashboards_data.sales.summary.payment_to_capture_count || widget.dashboards_data.sales.summary.order_carts_abandoned_count">
<div t-if="widget.dashboards_data.sales.summary.order_unpaid_count" class="o_inner_box o_dashboard_action" title="Confirm orders when you get paid." name="website_sale.action_unpaid_orders_ecommerce">
<div class="o_highlight"><t t-esc="widget.dashboards_data.sales.summary.order_unpaid_count"/></div>
Unpaid Orders
</div>
<div t-if="widget.dashboards_data.sales.summary.order_to_invoice_count" class="o_inner_box o_dashboard_action" title="Generate an invoice from orders ready for invoicing." name="website_sale.sale_order_action_to_invoice">
<div class="o_highlight"><t t-esc="widget.dashboards_data.sales.summary.order_to_invoice_count"/></div>
Orders to Invoice
</div>
<div t-if="widget.dashboards_data.sales.summary.payment_to_capture_count" class="o_inner_box o_dashboard_action" title="Capture order payments when the delivery is completed." name="website_sale.payment_transaction_action_payments_to_capture">
<div class="o_highlight"><t t-esc="widget.dashboards_data.sales.summary.payment_to_capture_count"/></div>
Payments to Capture
</div>
<div t-if="widget.dashboards_data.sales.summary.order_carts_abandoned_count" class="o_inner_box o_dashboard_action" title="Send a recovery email to visitors who haven't completed their order." name="website_sale.action_view_abandoned_tree">
<div class="o_highlight"><t t-esc="widget.dashboards_data.sales.summary.order_carts_abandoned_count"/></div>
Abandoned Carts
</div>
</div>
</t>
</t>
<t t-name="website_sale.dashboard_content" t-inherit="website.dashboard_content" t-inherit-mode="extension">
<xpath expr="//div[hasclass('o_website_dashboard_content')]/*[1]" position="before">
<div t-if="widget.groups.sale_salesman" class="row o_dashboard_sales">
<div class="col-12 row o_box">
<t t-if="widget.dashboards_data.sales.summary.order_count">
<h2 class="col-lg-7 col-12">
<t t-if="widget.date_range=='week'">
Sales Since Last Week
</t>
<t t-elif="widget.date_range=='month'">
Sales Since Last Month
</t>
<t t-elif="widget.date_range=='year'">
Sales Since Last Year
</t>
<t t-else="">Sales</t>
</h2>
<h4 class='col-lg-5 col-12'>AT A GLANCE</h4>
<div class="col-lg-7 col-12">
<div class="o_graph_sales" data-type="sales"/>
</div>
<div class="col-lg-5 col-12">
<t t-call="website_sale.products_table"/>
</div>
</t>
<t t-if="! widget.dashboards_data.sales.summary.order_count">
<t t-if="widget.date_range=='week'">
<h2>Sales Since Last Week</h2>
</t>
<t t-elif="widget.date_range=='month'">
<h2>Sales Since Last Month</h2>
</t>
<t t-elif="widget.date_range=='year'">
<h2>Sales Since Last Year</h2>
</t>
<t t-else=""><h2>Sales</h2></t>
<div class="col-lg-12 col-12">
<div class="o_demo_background">
</div>
<div class="o_demo_message">
<h3>There is no recent confirmed order.</h3>
</div>
</div>
</t>
</div>
</div>
</xpath>
</t>
<t t-name="website_sale.products_table">
<div class="row">
<a href="#" class="col-md-4 o_dashboard_action" name="website_sale.sale_report_action_dashboard">
<div class="o_link_enable" title="Orders">
<div class="o_highlight">
<t t-esc="widget.dashboards_data.sales.summary.order_count"/>
</div>
Orders
</div>
</a>
<a href="#" class="col-md-4 o_dashboard_action" name="website_sale.sale_report_action_dashboard">
<div class="o_link_enable" title="Untaxed Total Sold">
<div class="o_highlight">
<t t-esc="widget.render_monetary_field(widget.dashboards_data.sales.summary.total_sold, widget.data.currency)"/>
</div>
Sold
</div>
</a>
<a href="#" class="col-md-4 o_dashboard_action" name="website_sale.sale_report_action_carts">
<div class="o_link_enable o_invisible_border" title="Carts">
<div class="o_highlight"><t t-esc="widget.dashboards_data.sales.summary.order_carts_count"/></div>
Carts
</div>
</a>
<div class="col-md-4 o_link_disable" title="Orders/Day">
<div class="o_highlight"><t t-esc="widget.dashboards_data.sales.summary.order_per_day_ratio"/></div>
Orders/Day
</div>
<div class="col-md-4 o_link_disable" title="Average Order">
<div class="o_highlight"><t t-esc="widget.render_monetary_field(widget.dashboards_data.sales.summary.order_sold_ratio, widget.data.currency)"/></div>
Average Order
</div>
<div class="col-md-4 o_link_disable o_invisible_border" title="Conversion">
<div class="o_highlight"><t t-esc="widget.format_number(widget.dashboards_data.sales.summary.order_convertion_pctg, 'float', [3, 2], '%')"/></div>
Conversion
</div>
</div>
<div class="col-lg-12 col-12 o_top_margin">
<div class="row">
<div class="col-lg-12 col-12">
<h4>Best Sellers</h4>
<table class="table table-responsive table-hover">
<tr>
<th>Product</th>
<th>Quantity</th>
<th>Sold</th>
</tr>
<tr class="o_product_template" t-foreach="widget.dashboards_data.sales.best_sellers" t-as="product" t-att-data-product-id="product.id">
<td><t t-esc="product.name"/></td>
<td><t t-esc="product.qty"/></td>
<td><t t-esc="widget.render_monetary_field(product.sales, widget.data.currency)"/></td>
</tr>
</table>
</div>
</div>
</div>
</t>
<t t-extend="website_sale.products_table">
<t t-jquery=".o_top_margin .row .col-12" t-operation="attributes">
<attribute name="class" value="col-lg-6 col-12" />
</t>
<t t-jquery=".o_top_margin .row" t-operation="append">
<div class="col-lg-6 col-12 o_dashboard_utms">
<div>
<h4 class="float-start">REVENUE BY</h4>
<t t-call="website_sale.LinkTrackersDropDown"/>
</div>
<div class="o_utm_no_data_img">
<img src="website_sale/static/src/img/website_sale_chart_demo.png" alt="There isn't any UTM tag detected in orders" class="utm_chart_image image-responsive mt8"/>
</div>
<div class="o_utm_data_graph"/>
</div>
</t>
</t>
<t t-name="website_sale.LinkTrackersDropDown">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle utm_dropdown ml4" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="true"><span class="utm_button_name">Campaigns</span>
</button>
<div class="dropdown-menu" role="menu" aria-labelledby="utm_dropdown">
<a name="campaign_id" class="dropdown-item js_utm_selector" role="menuitem">Campaigns</a>
<a name="medium_id" class="dropdown-item js_utm_selector" role="menuitem">Medium</a>
<a name="source_id" class="dropdown-item js_utm_selector" role="menuitem">Sources</a>
</div>
</div>
</t>
<!-- Show the date range buttons related to the 'community' Sales Dashboard -->
<t t-extend="website.DateRangeButtons">
<t t-jquery="t[t-set='show_range_buttons']" t-operation="attributes">
<attribute name="t-value" value="1" />
</t>
</t>
</templates>

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="website_sale.ProductImageViewer" owl="1">
<div class="o_dialog" t-att-id="id" t-att-class="{ o_inactive_modal: !data.isActive }">
<div role="dialog" class="modal" t-ref="modalRef">
<div class="o_wsale_image_viewer flex-column align-items-center d-flex w-100 h-100" t-on-mousemove="onGlobalMousemove">
<!-- Header -->
<div class="o_wsale_image_viewer_header d-flex w-100 text-white">
<div class="flex-grow-1"/>
<div class="d-flex align-items-center mb-0 px-3 h4 text-reset cursor-pointer">
<span class="fa fa-times" t-on-click="data.close"/>
</div>
</div>
<!-- Content -->
<div class="o_wsale_image_viewer_image position-absolute top-0 bottom-0 start-0 end-0 align-items-center justify-content-center d-flex o_with_img overflow-hidden">
<div class="o_wsale_image_viewer_void position-absolute align-items-center justify-content-center d-flex w-100 h-100" t-ref="imageContainer" t-att-style="imageContainerStyle">
<img class="mw-100 mh-100 bg-black transition-base" t-att-src="selectedImage.src" draggable="false" alt="Viewer" t-att-style="imageStyle" t-on-wheel.stop="onWheelImage" t-on-mousedown="onMousedownImage"/>
</div>
</div>
<t t-if="images.length > 1">
<!-- Footer -->
<div class="o_wsale_image_viewer_carousel position-absolute bottom-0 d-flex" role="toolbar">
<ol class="d-flex justify-content-start ps-0 pt-2 pt-lg-0 mx-auto my-0 text-start">
<t t-foreach="images" t-as="image" t-key="image.thumbnailSrc">
<li t-attf-class="align-top position-relative px-1 pb-1 {{image === selectedImage ? 'active' : ''}}" t-on-click="() => this.selectedImage = image">
<div>
<img t-att-src="image.thumbnailSrc" t-attf-class="img o_wsale_image_viewer_thumbnail {{image === selectedImage ? 'active' : ''}}" t-att-alt="props.title" loading="lazy"/>
</div>
</li>
</t>
</ol>
</div>
<!-- Controls -->
<div class="o_wsale_image_viewer_control o_wsale_image_viewer_previous btn btn-dark position-absolute top-0 bottom-0 start-0 align-items-center justify-content-center d-flex my-auto ms-3 rounded-circle" t-on-click="previousImage" title="Previous (Left-Arrow)" role="button">
<span class="fa fa-chevron-left" role="img"/>
</div>
<div class="o_wsale_image_viewer_control o_wsale_image_viewer_next btn btn-dark position-absolute top-0 bottom-0 end-0 align-items-center justify-content-center d-flex my-auto me-3 rounded-circle" t-on-click="nextImage" title="Next (Right-Arrow)" role="button">
<span class="fa fa-chevron-right" role="img"/>
</div>
</t>
</div>
</div>
</div>
</t>
</templates>

View file

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates>
<t t-name="website_sale.ReorderModal" owl="1">
<Dialog title="env._t('Re-Order')">
<table id="o_wsale_reorder_table" class="table table-sm">
<thead class="bg-100">
<tr id="o_wsale_reorder_header">
<!-- Product Image -->
<th class="text-start td-img">Product</th>
<!-- Product name + description -->
<th/>
<!-- Product Quantity Selector -->
<th class="text-center td-qty">Quantity</th>
<!-- Product price (per unit) -->
<th class="text-end td-price">Price</th>
</tr>
</thead>
<tbody id="o_wsale_reorder_body" class="sale_tbody">
<t t-foreach="content.products" t-as="product" t-key="product_index">
<tr class="js_product">
<td t-if="product.has_image" class="td-img">
<img class="product_detail_img" t-att-alt="product.name" t-attf-src="/web/image/product.product/{{product.product_id}}/image_128"/>
</td>
<td t-att-colspan="product.has_image ? '1' : '2'">
<h5><t t-esc="product.name"/></h5>
<span class="text-muted d-none d-md-inline-block" t-if="product.description_sale" t-out="product.description_sale"/>
</td>
<t t-if="product.add_to_cart_allowed">
<td class="text-center td-qty">
<div class="css_quantity input-group input-group-sm justify-content-center">
<a href="#" class="btn btn-link d-none d-md-inline-block" aria-label="Remove one" title="Remove one" t-on-click.stop.prevent="() => this.changeProductQty(product, product.qty - 1)">
<i class="fa fa-minus"/>
</a>
<input type="text" class="js_quantity text-center form-control quantity" t-on-change="(ev) => this.onChangeProductQtyInput(ev, product)"
t-att-value="product.qty"/>
<a href="#" class="btn btn-link d-none d-md-inline-block" aria-label="Add one" title="Add one" t-on-click.stop.prevent="() => this.changeProductQty(product, product.qty + 1)">
<i class="fa fa-plus"/>
</a>
</div>
<div t-if="product.qty_warning and product.qty_warning !== ''" class="text-warning fw-bold">
<i class="fa fa-exclamation-triangle"/>
<span t-esc="product.qty_warning"/>
</div>
</td>
<td class="text-end td-price">
<span t-esc="formatMonetary(product.combinationInfo.price, content.currency)"/>
</td>
</t>
<t t-else="">
<td class="text-center" colspan="2">
<div class="text-warning fw-bold">
<i class="fa fa-exclamation-triangle"/>
<span t-esc="getWarningForProduct(product)"/>
</div>
</td>
</t>
</tr>
</t>
</tbody>
</table>
<div id="o_wsale_reorder_total" class="row" name="total" style="page-break-inside: avoid;">
<div class="col-sm-7 col-md-6 ms-auto">
<table class="table table-sm">
<tr class="border-black o_total">
<td>
<strong>Total</strong>
</td>
<td class="text-end">
<span t-out="formatMonetary(total, content.currency)"/>
</td>
</tr>
</table>
</div>
</div>
<t t-set-slot="footer">
<button class="btn btn-primary o_wsale_reorder_confirm" t-att-disabled="!hasBuyableProducts" t-on-click="confirmReorder">
Add To Cart
</button>
<button class="btn btn-secondary o_wsale_reorder_cancel" t-on-click.stop.prevent="props.close">
Discard
</button>
</t>
</Dialog>
</t>
<t t-name="website_sale.ReorderConfirmationDialog" t-inherit="web.ConfirmationDialog" t-inherit-mode="primary" owl="1">
<xpath expr="//button[1]" position="replace">
<button class="btn btn-primary" t-on-click="_confirm">
Yes
</button>
</xpath>
<xpath expr="//button[2]" position="replace">
<button class="btn btn-secondary" t-on-click="_cancel">
No
</button>
</xpath>
</t>
</templates>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates id="template" xml:space="preserve">
<!-- Products Search Bar autocomplete item -->
<we-button t-name="website_sale.ribbonSelectItem" t-att-data-set-ribbon="ribbon.id">
<t t-out="ribbon.html"/>
<span t-attf-class="fa fa-#{isTag ? 'tag' : 'bookmark'} ms-auto"></span>
<span t-attf-class="fa fa-arrow-#{isLeft ? 'left' : 'right'} ms-1"></span>
<span t-attf-class="o_wsale_color_preview #{colorClasses} ms-1" t-attf-style="background-color: #{ribbon.bg_color}"></span>
<span t-attf-class="o_wsale_color_preview #{colorClasses} ms-1" t-attf-style="background-color: #{textColor} !important;"></span>
</we-button>
</templates>