Merge branch 'v2-shop' into v2

This commit is contained in:
2021-11-10 23:15:56 +01:00
55 changed files with 1719 additions and 813 deletions

View File

@@ -16,6 +16,10 @@ VITE_API_URL_PROD="https://api.housesof.world"
VITE_API_GRAPHQL_PATH="/graphql" VITE_API_GRAPHQL_PATH="/graphql"
VITE_API_TOKEN="efa40490-152c-49d7-a75b-30a6427439b1" VITE_API_TOKEN="efa40490-152c-49d7-a75b-30a6427439b1"
# Shop
VITE_SWELL_STORE_ID="houses-of"
VITE_SWELL_API_TOKEN="v3BiXcZP5jpmhL80i4eUy6iXxcpN9cIq"
# Analytics # Analytics
VITE_ANALYTICS_KEY="c01e378821e6ba7bf9a9f947b107500bfcbd4ae8" VITE_ANALYTICS_KEY="c01e378821e6ba7bf9a9f947b107500bfcbd4ae8"
VITE_ANALYTICS_URL="https://stats.flayks.com" VITE_ANALYTICS_URL="https://stats.flayks.com"

View File

@@ -26,14 +26,15 @@
"@sveltejs/adapter-node": "next", "@sveltejs/adapter-node": "next",
"@sveltejs/adapter-vercel": "next", "@sveltejs/adapter-vercel": "next",
"@sveltejs/kit": "next", "@sveltejs/kit": "next",
"@typescript-eslint/eslint-plugin": "^5.3.0", "@typescript-eslint/eslint-plugin": "^5.3.1",
"@typescript-eslint/parser": "^5.3.0", "@typescript-eslint/parser": "^5.3.1",
"eslint": "^8.2.0", "eslint": "^8.2.0",
"eslint-plugin-svelte3": "^3.2.1", "eslint-plugin-svelte3": "^3.2.1",
"sass": "^1.43.4", "sass": "^1.43.4",
"svelte": "^3.44.1", "svelte": "^3.44.1",
"svelte-check": "^2.2.8", "svelte-check": "^2.2.8",
"svelte-preprocess": "^4.9.8", "svelte-preprocess": "^4.9.8",
"swell-node": "^4.0.6",
"tslib": "^2.3.1", "tslib": "^2.3.1",
"typescript": "^4.4.4" "typescript": "^4.4.4"
}, },

234
pnpm-lock.yaml generated
View File

@@ -4,8 +4,8 @@ specifiers:
'@sveltejs/adapter-node': next '@sveltejs/adapter-node': next
'@sveltejs/adapter-vercel': next '@sveltejs/adapter-vercel': next
'@sveltejs/kit': next '@sveltejs/kit': next
'@typescript-eslint/eslint-plugin': ^5.3.0 '@typescript-eslint/eslint-plugin': ^5.3.1
'@typescript-eslint/parser': ^5.3.0 '@typescript-eslint/parser': ^5.3.1
dayjs: ^1.10.7 dayjs: ^1.10.7
embla-carousel: ^5.0.1 embla-carousel: ^5.0.1
eslint: ^8.2.0 eslint: ^8.2.0
@@ -16,6 +16,7 @@ specifiers:
svelte: ^3.44.1 svelte: ^3.44.1
svelte-check: ^2.2.8 svelte-check: ^2.2.8
svelte-preprocess: ^4.9.8 svelte-preprocess: ^4.9.8
swell-node: ^4.0.6
tslib: ^2.3.1 tslib: ^2.3.1
typescript: ^4.4.4 typescript: ^4.4.4
@@ -29,14 +30,15 @@ devDependencies:
'@sveltejs/adapter-node': 1.0.0-next.55 '@sveltejs/adapter-node': 1.0.0-next.55
'@sveltejs/adapter-vercel': 1.0.0-next.31 '@sveltejs/adapter-vercel': 1.0.0-next.31
'@sveltejs/kit': 1.0.0-next.195_sass@1.43.4+svelte@3.44.1 '@sveltejs/kit': 1.0.0-next.195_sass@1.43.4+svelte@3.44.1
'@typescript-eslint/eslint-plugin': 5.3.0_c2f57e23dad63fa3be14ade7e20e2af7 '@typescript-eslint/eslint-plugin': 5.3.1_4653b7803b7453f5f37717b7e1448517
'@typescript-eslint/parser': 5.3.0_eslint@8.2.0+typescript@4.4.4 '@typescript-eslint/parser': 5.3.1_eslint@8.2.0+typescript@4.4.4
eslint: 8.2.0 eslint: 8.2.0
eslint-plugin-svelte3: 3.2.1_eslint@8.2.0+svelte@3.44.1 eslint-plugin-svelte3: 3.2.1_eslint@8.2.0+svelte@3.44.1
sass: 1.43.4 sass: 1.43.4
svelte: 3.44.1 svelte: 3.44.1
svelte-check: 2.2.8_sass@1.43.4+svelte@3.44.1 svelte-check: 2.2.8_sass@1.43.4+svelte@3.44.1
svelte-preprocess: 4.9.8_6627cbae993b0086cf4555994e082905 svelte-preprocess: 4.9.8_6627cbae993b0086cf4555994e082905
swell-node: 4.0.6
tslib: 2.3.1 tslib: 2.3.1
typescript: 4.4.4 typescript: 4.4.4
@@ -106,14 +108,14 @@ packages:
/@sveltejs/adapter-node/1.0.0-next.55: /@sveltejs/adapter-node/1.0.0-next.55:
resolution: {integrity: sha512-Kmh8lx8kIY7W6rkqjC78y4dQTyjiAHD9D1WfmUTtYuDW1jAIG+YbZmPC9127kH5KOSGK4+tI8mpReDVB1lgf8g==} resolution: {integrity: sha512-Kmh8lx8kIY7W6rkqjC78y4dQTyjiAHD9D1WfmUTtYuDW1jAIG+YbZmPC9127kH5KOSGK4+tI8mpReDVB1lgf8g==}
dependencies: dependencies:
esbuild: 0.13.12 esbuild: 0.13.13
tiny-glob: 0.2.9 tiny-glob: 0.2.9
dev: true dev: true
/@sveltejs/adapter-vercel/1.0.0-next.31: /@sveltejs/adapter-vercel/1.0.0-next.31:
resolution: {integrity: sha512-W8p/U00B6ihVrDpwMkgEexfVUzaLmn4MRtXj//Gw4NDFfsrZR4P5wBidrOAIkCYBMvqCOHD+vbAvIiAMOaN23g==} resolution: {integrity: sha512-W8p/U00B6ihVrDpwMkgEexfVUzaLmn4MRtXj//Gw4NDFfsrZR4P5wBidrOAIkCYBMvqCOHD+vbAvIiAMOaN23g==}
dependencies: dependencies:
esbuild: 0.13.12 esbuild: 0.13.13
dev: true dev: true
/@sveltejs/kit/1.0.0-next.195_sass@1.43.4+svelte@3.44.1: /@sveltejs/kit/1.0.0-next.195_sass@1.43.4+svelte@3.44.1:
@@ -123,11 +125,11 @@ packages:
peerDependencies: peerDependencies:
svelte: ^3.44.0 svelte: ^3.44.0
dependencies: dependencies:
'@sveltejs/vite-plugin-svelte': 1.0.0-next.30_svelte@3.44.1+vite@2.6.13 '@sveltejs/vite-plugin-svelte': 1.0.0-next.30_svelte@3.44.1+vite@2.6.14
cheap-watch: 1.0.4 cheap-watch: 1.0.4
sade: 1.7.4 sade: 1.7.4
svelte: 3.44.1 svelte: 3.44.1
vite: 2.6.13_sass@1.43.4 vite: 2.6.14_sass@1.43.4
transitivePeerDependencies: transitivePeerDependencies:
- diff-match-patch - diff-match-patch
- less - less
@@ -136,7 +138,7 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@sveltejs/vite-plugin-svelte/1.0.0-next.30_svelte@3.44.1+vite@2.6.13: /@sveltejs/vite-plugin-svelte/1.0.0-next.30_svelte@3.44.1+vite@2.6.14:
resolution: {integrity: sha512-YQqdMxjL1VgSFk4/+IY3yLwuRRapPafPiZTiaGEq1psbJYSNYUWx9F1zMm32GMsnogg3zn99mGJOqe3ld3HZSg==} resolution: {integrity: sha512-YQqdMxjL1VgSFk4/+IY3yLwuRRapPafPiZTiaGEq1psbJYSNYUWx9F1zMm32GMsnogg3zn99mGJOqe3ld3HZSg==}
engines: {node: ^14.13.1 || >= 16} engines: {node: ^14.13.1 || >= 16}
peerDependencies: peerDependencies:
@@ -154,7 +156,7 @@ packages:
require-relative: 0.8.7 require-relative: 0.8.7
svelte: 3.44.1 svelte: 3.44.1
svelte-hmr: 0.14.7_svelte@3.44.1 svelte-hmr: 0.14.7_svelte@3.44.1
vite: 2.6.13_sass@1.43.4 vite: 2.6.14_sass@1.43.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@@ -163,8 +165,8 @@ packages:
resolution: {integrity: sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==} resolution: {integrity: sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==}
dev: true dev: true
/@types/node/16.11.6: /@types/node/16.11.7:
resolution: {integrity: sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==} resolution: {integrity: sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==}
dev: true dev: true
/@types/pug/2.0.5: /@types/pug/2.0.5:
@@ -174,11 +176,11 @@ packages:
/@types/sass/1.43.0: /@types/sass/1.43.0:
resolution: {integrity: sha512-DPSXNJ1rYLo88GyF9tuB4bsYGfpKI1a4+wOQmc+LI1SUoocm9QLRSpz0GxxuyjmJsYFIQo/dDlRSSpIXngff+w==} resolution: {integrity: sha512-DPSXNJ1rYLo88GyF9tuB4bsYGfpKI1a4+wOQmc+LI1SUoocm9QLRSpz0GxxuyjmJsYFIQo/dDlRSSpIXngff+w==}
dependencies: dependencies:
'@types/node': 16.11.6 '@types/node': 16.11.7
dev: true dev: true
/@typescript-eslint/eslint-plugin/5.3.0_c2f57e23dad63fa3be14ade7e20e2af7: /@typescript-eslint/eslint-plugin/5.3.1_4653b7803b7453f5f37717b7e1448517:
resolution: {integrity: sha512-ARUEJHJrq85aaiCqez7SANeahDsJTD3AEua34EoQN9pHS6S5Bq9emcIaGGySt/4X2zSi+vF5hAH52sEen7IO7g==} resolution: {integrity: sha512-cFImaoIr5Ojj358xI/SDhjog57OK2NqlpxwdcgyxDA3bJlZcJq5CPzUXtpD7CxI2Hm6ATU7w5fQnnkVnmwpHqw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
'@typescript-eslint/parser': ^5.0.0 '@typescript-eslint/parser': ^5.0.0
@@ -188,9 +190,9 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/experimental-utils': 5.3.0_eslint@8.2.0+typescript@4.4.4 '@typescript-eslint/experimental-utils': 5.3.1_eslint@8.2.0+typescript@4.4.4
'@typescript-eslint/parser': 5.3.0_eslint@8.2.0+typescript@4.4.4 '@typescript-eslint/parser': 5.3.1_eslint@8.2.0+typescript@4.4.4
'@typescript-eslint/scope-manager': 5.3.0 '@typescript-eslint/scope-manager': 5.3.1
debug: 4.3.2 debug: 4.3.2
eslint: 8.2.0 eslint: 8.2.0
functional-red-black-tree: 1.0.1 functional-red-black-tree: 1.0.1
@@ -203,16 +205,16 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/experimental-utils/5.3.0_eslint@8.2.0+typescript@4.4.4: /@typescript-eslint/experimental-utils/5.3.1_eslint@8.2.0+typescript@4.4.4:
resolution: {integrity: sha512-NFVxYTjKj69qB0FM+piah1x3G/63WB8vCBMnlnEHUsiLzXSTWb9FmFn36FD9Zb4APKBLY3xRArOGSMQkuzTF1w==} resolution: {integrity: sha512-RgFn5asjZ5daUhbK5Sp0peq0SSMytqcrkNfU4pnDma2D8P3ElZ6JbYjY8IMSFfZAJ0f3x3tnO3vXHweYg0g59w==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: '*' eslint: '*'
dependencies: dependencies:
'@types/json-schema': 7.0.9 '@types/json-schema': 7.0.9
'@typescript-eslint/scope-manager': 5.3.0 '@typescript-eslint/scope-manager': 5.3.1
'@typescript-eslint/types': 5.3.0 '@typescript-eslint/types': 5.3.1
'@typescript-eslint/typescript-estree': 5.3.0_typescript@4.4.4 '@typescript-eslint/typescript-estree': 5.3.1_typescript@4.4.4
eslint: 8.2.0 eslint: 8.2.0
eslint-scope: 5.1.1 eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@8.2.0 eslint-utils: 3.0.0_eslint@8.2.0
@@ -221,8 +223,8 @@ packages:
- typescript - typescript
dev: true dev: true
/@typescript-eslint/parser/5.3.0_eslint@8.2.0+typescript@4.4.4: /@typescript-eslint/parser/5.3.1_eslint@8.2.0+typescript@4.4.4:
resolution: {integrity: sha512-rKu/yAReip7ovx8UwOAszJVO5MgBquo8WjIQcp1gx4pYQCwYzag+I5nVNHO4MqyMkAo0gWt2gWUi+36gWAVKcw==} resolution: {integrity: sha512-TD+ONlx5c+Qhk21x9gsJAMRohWAUMavSOmJgv3JGy9dgPhuBd5Wok0lmMClZDyJNLLZK1JRKiATzCKZNUmoyfw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -231,9 +233,9 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/scope-manager': 5.3.0 '@typescript-eslint/scope-manager': 5.3.1
'@typescript-eslint/types': 5.3.0 '@typescript-eslint/types': 5.3.1
'@typescript-eslint/typescript-estree': 5.3.0_typescript@4.4.4 '@typescript-eslint/typescript-estree': 5.3.1_typescript@4.4.4
debug: 4.3.2 debug: 4.3.2
eslint: 8.2.0 eslint: 8.2.0
typescript: 4.4.4 typescript: 4.4.4
@@ -241,21 +243,21 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/scope-manager/5.3.0: /@typescript-eslint/scope-manager/5.3.1:
resolution: {integrity: sha512-22Uic9oRlTsPppy5Tcwfj+QET5RWEnZ5414Prby465XxQrQFZ6nnm5KnXgnsAJefG4hEgMnaxTB3kNEyjdjj6A==} resolution: {integrity: sha512-XksFVBgAq0Y9H40BDbuPOTUIp7dn4u8oOuhcgGq7EoDP50eqcafkMVGrypyVGvDYHzjhdUCUwuwVUK4JhkMAMg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 5.3.0 '@typescript-eslint/types': 5.3.1
'@typescript-eslint/visitor-keys': 5.3.0 '@typescript-eslint/visitor-keys': 5.3.1
dev: true dev: true
/@typescript-eslint/types/5.3.0: /@typescript-eslint/types/5.3.1:
resolution: {integrity: sha512-fce5pG41/w8O6ahQEhXmMV+xuh4+GayzqEogN24EK+vECA3I6pUwKuLi5QbXO721EMitpQne5VKXofPonYlAQg==} resolution: {integrity: sha512-bG7HeBLolxKHtdHG54Uac750eXuQQPpdJfCYuw4ZI3bZ7+GgKClMWM8jExBtp7NSP4m8PmLRM8+lhzkYnSmSxQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/@typescript-eslint/typescript-estree/5.3.0_typescript@4.4.4: /@typescript-eslint/typescript-estree/5.3.1_typescript@4.4.4:
resolution: {integrity: sha512-FJ0nqcaUOpn/6Z4Jwbtf+o0valjBLkqc3MWkMvrhA2TvzFXtcclIM8F4MBEmYa2kgcI8EZeSAzwoSrIC8JYkug==} resolution: {integrity: sha512-PwFbh/PKDVo/Wct6N3w+E4rLZxUDgsoII/GrWM2A62ETOzJd4M6s0Mu7w4CWsZraTbaC5UQI+dLeyOIFF1PquQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
@@ -263,8 +265,8 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/types': 5.3.0 '@typescript-eslint/types': 5.3.1
'@typescript-eslint/visitor-keys': 5.3.0 '@typescript-eslint/visitor-keys': 5.3.1
debug: 4.3.2 debug: 4.3.2
globby: 11.0.4 globby: 11.0.4
is-glob: 4.0.3 is-glob: 4.0.3
@@ -275,12 +277,12 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/visitor-keys/5.3.0: /@typescript-eslint/visitor-keys/5.3.1:
resolution: {integrity: sha512-oVIAfIQuq0x2TFDNLVavUn548WL+7hdhxYn+9j3YdJJXB7mH9dAmZNJsPDa7Jc+B9WGqoiex7GUDbyMxV0a/aw==} resolution: {integrity: sha512-3cHUzUuVTuNHx0Gjjt5pEHa87+lzyqOiHXy/Gz+SJOCW1mpw9xQHIIEwnKn+Thph1mgWyZ90nboOcSuZr/jTTQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
'@typescript-eslint/types': 5.3.0 '@typescript-eslint/types': 5.3.1
eslint-visitor-keys: 3.0.0 eslint-visitor-keys: 3.1.0
dev: true dev: true
/acorn-jsx/5.3.2_acorn@8.5.0: /acorn-jsx/5.3.2_acorn@8.5.0:
@@ -478,164 +480,147 @@ packages:
resolution: {integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=} resolution: {integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=}
dev: true dev: true
/esbuild-android-arm64/0.13.12: /esbuild-android-arm64/0.13.13:
resolution: {integrity: sha512-TSVZVrb4EIXz6KaYjXfTzPyyRpXV5zgYIADXtQsIenjZ78myvDGaPi11o4ZSaHIwFHsuwkB6ne5SZRBwAQ7maw==} resolution: {integrity: sha512-T02aneWWguJrF082jZworjU6vm8f4UQ+IH2K3HREtlqoY9voiJUwHLRL6khRlsNLzVglqgqb7a3HfGx7hAADCQ==}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-darwin-64/0.13.12: /esbuild-darwin-64/0.13.13:
resolution: {integrity: sha512-c51C+N+UHySoV2lgfWSwwmlnLnL0JWj/LzuZt9Ltk9ub1s2Y8cr6SQV5W3mqVH1egUceew6KZ8GyI4nwu+fhsw==} resolution: {integrity: sha512-wkaiGAsN/09X9kDlkxFfbbIgR78SNjMOfUhoel3CqKBDsi9uZhw7HBNHNxTzYUK8X8LAKFpbODgcRB3b/I8gHA==}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-darwin-arm64/0.13.12: /esbuild-darwin-arm64/0.13.13:
resolution: {integrity: sha512-JvAMtshP45Hd8A8wOzjkY1xAnTKTYuP/QUaKp5eUQGX+76GIie3fCdUUr2ZEKdvpSImNqxiZSIMziEiGB5oUmQ==} resolution: {integrity: sha512-b02/nNKGSV85Gw9pUCI5B48AYjk0vFggDeom0S6QMP/cEDtjSh1WVfoIFNAaLA0MHWfue8KBwoGVsN7rBshs4g==}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-freebsd-64/0.13.12: /esbuild-freebsd-64/0.13.13:
resolution: {integrity: sha512-r6On/Skv9f0ZjTu6PW5o7pdXr8aOgtFOEURJZYf1XAJs0IQ+gW+o1DzXjVkIoT+n1cm3N/t1KRJfX71MPg/ZUA==} resolution: {integrity: sha512-ALgXYNYDzk9YPVk80A+G4vz2D22Gv4j4y25exDBGgqTcwrVQP8rf/rjwUjHoh9apP76oLbUZTmUmvCMuTI1V9A==}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-freebsd-arm64/0.13.12: /esbuild-freebsd-arm64/0.13.13:
resolution: {integrity: sha512-F6LmI2Q1gii073kmBE3NOTt/6zLL5zvZsxNLF8PMAwdHc+iBhD1vzfI8uQZMJA1IgXa3ocr3L3DJH9fLGXy6Yw==} resolution: {integrity: sha512-uFvkCpsZ1yqWQuonw5T1WZ4j59xP/PCvtu6I4pbLejhNo4nwjW6YalqnBvBSORq5/Ifo9S/wsIlVHzkzEwdtlw==}
cpu: [arm64] cpu: [arm64]
os: [freebsd] os: [freebsd]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-linux-32/0.13.12: /esbuild-linux-32/0.13.13:
resolution: {integrity: sha512-U1UZwG3UIwF7/V4tCVAo/nkBV9ag5KJiJTt+gaCmLVWH3bPLX7y+fNlhIWZy8raTMnXhMKfaTvWZ9TtmXzvkuQ==} resolution: {integrity: sha512-yxR9BBwEPs9acVEwTrEE2JJNHYVuPQC9YGjRfbNqtyfK/vVBQYuw8JaeRFAvFs3pVJdQD0C2BNP4q9d62SCP4w==}
cpu: [ia32] cpu: [ia32]
os: [linux] os: [linux]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-linux-64/0.13.12: /esbuild-linux-64/0.13.13:
resolution: {integrity: sha512-YpXSwtu2NxN3N4ifJxEdsgd6Q5d8LYqskrAwjmoCT6yQnEHJSF5uWcxv783HWN7lnGpJi9KUtDvYsnMdyGw71Q==} resolution: {integrity: sha512-kzhjlrlJ+6ESRB/n12WTGll94+y+HFeyoWsOrLo/Si0s0f+Vip4b8vlnG0GSiS6JTsWYAtGHReGczFOaETlKIw==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-linux-arm/0.13.12: /esbuild-linux-arm/0.13.13:
resolution: {integrity: sha512-SyiT/JKxU6J+DY2qUiSLZJqCAftIt3uoGejZ0HDnUM2MGJqEGSGh7p1ecVL2gna3PxS4P+j6WAehCwgkBPXNIw==} resolution: {integrity: sha512-hXub4pcEds+U1TfvLp1maJ+GHRw7oizvzbGRdUvVDwtITtjq8qpHV5Q5hWNNn6Q+b3b2UxF03JcgnpzCw96nUQ==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-linux-arm64/0.13.12: /esbuild-linux-arm64/0.13.13:
resolution: {integrity: sha512-sgDNb8kb3BVodtAlcFGgwk+43KFCYjnFOaOfJibXnnIojNWuJHpL6aQJ4mumzNWw8Rt1xEtDQyuGK9f+Y24jGA==} resolution: {integrity: sha512-KMrEfnVbmmJxT3vfTnPv/AiXpBFbbyExH13BsUGy1HZRPFMi5Gev5gk8kJIZCQSRfNR17aqq8sO5Crm2KpZkng==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-linux-mips64le/0.13.12: /esbuild-linux-mips64le/0.13.13:
resolution: {integrity: sha512-qQJHlZBG+QwVIA8AbTEtbvF084QgDi4DaUsUnA+EolY1bxrG+UyOuGflM2ZritGhfS/k7THFjJbjH2wIeoKA2g==} resolution: {integrity: sha512-cJT9O1LYljqnnqlHaS0hdG73t7hHzF3zcN0BPsjvBq+5Ad47VJun+/IG4inPhk8ta0aEDK6LdP+F9299xa483w==}
cpu: [mips64el] cpu: [mips64el]
os: [linux] os: [linux]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-linux-ppc64le/0.13.12: /esbuild-linux-ppc64le/0.13.13:
resolution: {integrity: sha512-2dSnm1ldL7Lppwlo04CGQUpwNn5hGqXI38OzaoPOkRsBRWFBozyGxTFSee/zHFS+Pdh3b28JJbRK3owrrRgWNw==} resolution: {integrity: sha512-+rghW8st6/7O6QJqAjVK3eXzKkZqYAw6LgHv7yTMiJ6ASnNvghSeOcIvXFep3W2oaJc35SgSPf21Ugh0o777qQ==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-netbsd-64/0.13.12: /esbuild-netbsd-64/0.13.13:
resolution: {integrity: sha512-D4raxr02dcRiQNbxOLzpqBzcJNFAdsDNxjUbKkDMZBkL54Z0vZh4LRndycdZAMcIdizC/l/Yp/ZsBdAFxc5nbA==} resolution: {integrity: sha512-A/B7rwmzPdzF8c3mht5TukbnNwY5qMJqes09ou0RSzA5/jm7Jwl/8z853ofujTFOLhkNHUf002EAgokzSgEMpQ==}
cpu: [x64] cpu: [x64]
os: [netbsd] os: [netbsd]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-openbsd-64/0.13.12: /esbuild-openbsd-64/0.13.13:
resolution: {integrity: sha512-KuLCmYMb2kh05QuPJ+va60bKIH5wHL8ypDkmpy47lzwmdxNsuySeCMHuTv5o2Af1RUn5KLO5ZxaZeq4GEY7DaQ==} resolution: {integrity: sha512-szwtuRA4rXKT3BbwoGpsff6G7nGxdKgUbW9LQo6nm0TVCCjDNDC/LXxT994duIW8Tyq04xZzzZSW7x7ttDiw1w==}
cpu: [x64] cpu: [x64]
os: [openbsd] os: [openbsd]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-sunos-64/0.13.12: /esbuild-sunos-64/0.13.13:
resolution: {integrity: sha512-jBsF+e0woK3miKI8ufGWKG3o3rY9DpHvCVRn5eburMIIE+2c+y3IZ1srsthKyKI6kkXLvV4Cf/E7w56kLipMXw==} resolution: {integrity: sha512-ihyds9O48tVOYF48iaHYUK/boU5zRaLOXFS+OOL3ceD39AyHo46HVmsJLc7A2ez0AxNZCxuhu+P9OxfPfycTYQ==}
cpu: [x64] cpu: [x64]
os: [sunos] os: [sunos]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-windows-32/0.13.12: /esbuild-windows-32/0.13.13:
resolution: {integrity: sha512-L9m4lLFQrFeR7F+eLZXG82SbXZfUhyfu6CexZEil6vm+lc7GDCE0Q8DiNutkpzjv1+RAbIGVva9muItQ7HVTkQ==} resolution: {integrity: sha512-h2RTYwpG4ldGVJlbmORObmilzL8EECy8BFiF8trWE1ZPHLpECE9//J3Bi+W3eDUuv/TqUbiNpGrq4t/odbayUw==}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-windows-64/0.13.12: /esbuild-windows-64/0.13.13:
resolution: {integrity: sha512-k4tX4uJlSbSkfs78W5d9+I9gpd+7N95W7H2bgOMFPsYREVJs31+Q2gLLHlsnlY95zBoPQMIzHooUIsixQIBjaQ==} resolution: {integrity: sha512-oMrgjP4CjONvDHe7IZXHrMk3wX5Lof/IwFEIbwbhgbXGBaN2dke9PkViTiXC3zGJSGpMvATXVplEhlInJ0drHA==}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild-windows-arm64/0.13.12: /esbuild-windows-arm64/0.13.13:
resolution: {integrity: sha512-2tTv/BpYRIvuwHpp2M960nG7uvL+d78LFW/ikPItO+2GfK51CswIKSetSpDii+cjz8e9iSPgs+BU4o8nWICBwQ==} resolution: {integrity: sha512-6fsDfTuTvltYB5k+QPah/x7LrI2+OLAJLE3bWLDiZI6E8wXMQU+wLqtEO/U/RvJgVY1loPs5eMpUBpVajczh1A==}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
requiresBuild: true
dev: true dev: true
optional: true optional: true
/esbuild/0.13.12: /esbuild/0.13.13:
resolution: {integrity: sha512-vTKKUt+yoz61U/BbrnmlG9XIjwpdIxmHB8DlPR0AAW6OdS+nBQBci6LUHU2q9WbBobMEIQxxDpKbkmOGYvxsow==} resolution: {integrity: sha512-Z17A/R6D0b4s3MousytQ/5i7mTCbaF+Ua/yPfoe71vdTv4KBvVAvQ/6ytMngM2DwGJosl8WxaD75NOQl2QF26Q==}
hasBin: true hasBin: true
requiresBuild: true requiresBuild: true
optionalDependencies: optionalDependencies:
esbuild-android-arm64: 0.13.12 esbuild-android-arm64: 0.13.13
esbuild-darwin-64: 0.13.12 esbuild-darwin-64: 0.13.13
esbuild-darwin-arm64: 0.13.12 esbuild-darwin-arm64: 0.13.13
esbuild-freebsd-64: 0.13.12 esbuild-freebsd-64: 0.13.13
esbuild-freebsd-arm64: 0.13.12 esbuild-freebsd-arm64: 0.13.13
esbuild-linux-32: 0.13.12 esbuild-linux-32: 0.13.13
esbuild-linux-64: 0.13.12 esbuild-linux-64: 0.13.13
esbuild-linux-arm: 0.13.12 esbuild-linux-arm: 0.13.13
esbuild-linux-arm64: 0.13.12 esbuild-linux-arm64: 0.13.13
esbuild-linux-mips64le: 0.13.12 esbuild-linux-mips64le: 0.13.13
esbuild-linux-ppc64le: 0.13.12 esbuild-linux-ppc64le: 0.13.13
esbuild-netbsd-64: 0.13.12 esbuild-netbsd-64: 0.13.13
esbuild-openbsd-64: 0.13.12 esbuild-openbsd-64: 0.13.13
esbuild-sunos-64: 0.13.12 esbuild-sunos-64: 0.13.13
esbuild-windows-32: 0.13.12 esbuild-windows-32: 0.13.13
esbuild-windows-64: 0.13.12 esbuild-windows-64: 0.13.13
esbuild-windows-arm64: 0.13.12 esbuild-windows-arm64: 0.13.13
dev: true dev: true
/escape-string-regexp/4.0.0: /escape-string-regexp/4.0.0:
@@ -685,8 +670,8 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/eslint-visitor-keys/3.0.0: /eslint-visitor-keys/3.1.0:
resolution: {integrity: sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q==} resolution: {integrity: sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
@@ -706,7 +691,7 @@ packages:
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 6.0.0 eslint-scope: 6.0.0
eslint-utils: 3.0.0_eslint@8.2.0 eslint-utils: 3.0.0_eslint@8.2.0
eslint-visitor-keys: 3.0.0 eslint-visitor-keys: 3.1.0
espree: 9.0.0 espree: 9.0.0
esquery: 1.4.0 esquery: 1.4.0
esutils: 2.0.3 esutils: 2.0.3
@@ -743,7 +728,7 @@ packages:
dependencies: dependencies:
acorn: 8.5.0 acorn: 8.5.0
acorn-jsx: 5.3.2_acorn@8.5.0 acorn-jsx: 5.3.2_acorn@8.5.0
eslint-visitor-keys: 3.0.0 eslint-visitor-keys: 3.1.0
dev: true dev: true
/esquery/1.4.0: /esquery/1.4.0:
@@ -826,12 +811,12 @@ packages:
resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
dependencies: dependencies:
flatted: 3.2.2 flatted: 3.2.4
rimraf: 3.0.2 rimraf: 3.0.2
dev: true dev: true
/flatted/3.2.2: /flatted/3.2.4:
resolution: {integrity: sha512-JaTY/wtrcSyvXJl4IMFHPKyFur1sE9AUqc0QnhOaJ0CxHtAoIV8pYDzeEfAaNEtGkOfq4gr3LBFmdXW5mOQFnA==} resolution: {integrity: sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==}
dev: true dev: true
/focus-visible/5.2.0: /focus-visible/5.2.0:
@@ -1437,6 +1422,11 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
dev: true dev: true
/swell-node/4.0.6:
resolution: {integrity: sha512-9eAjxse63TL2J3R7RdyD3VoykSffkY/z4jpIpwqjGUhbJYhpqXwbAive2U+6dvdqxGdjovBM8siTeld2Ud9LVw==}
engines: {node: '>= v12.21.0'}
dev: true
/text-table/0.2.0: /text-table/0.2.0:
resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=} resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=}
dev: true dev: true
@@ -1501,8 +1491,8 @@ packages:
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
dev: true dev: true
/vite/2.6.13_sass@1.43.4: /vite/2.6.14_sass@1.43.4:
resolution: {integrity: sha512-+tGZ1OxozRirTudl4M3N3UTNJOlxdVo/qBl2IlDEy/ZpTFcskp+k5ncNjayR3bRYTCbqSOFz2JWGN1UmuDMScA==} resolution: {integrity: sha512-2HA9xGyi+EhY2MXo0+A2dRsqsAG3eFNEVIo12olkWhOmc8LfiM+eMdrXf+Ruje9gdXgvSqjLI9freec1RUM5EA==}
engines: {node: '>=12.2.0'} engines: {node: '>=12.2.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -1517,7 +1507,7 @@ packages:
stylus: stylus:
optional: true optional: true
dependencies: dependencies:
esbuild: 0.13.12 esbuild: 0.13.13
postcss: 8.3.11 postcss: 8.3.11
resolve: 1.20.0 resolve: 1.20.0
rollup: 2.59.0 rollup: 2.59.0

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import Icon from '$components/atoms/Icon.svelte'
export let icon: string export let icon: string
export let alt: string export let alt: string
export let label: string export let label: string
@@ -7,7 +9,7 @@
<a href={url} class="box-cta"> <a href={url} class="box-cta">
<div class="icon"> <div class="icon">
<img src={icon} alt={alt} width={48} height={48} loading="lazy"> <Icon icon={icon} />
</div> </div>
<span class="text-label"> <span class="text-label">
{label} {label}

View File

@@ -34,6 +34,7 @@
href={url} class={classes} href={url} class={classes}
{rel} {target} {rel} {target}
sveltekit:prefetch={url && isExternal ? null : true} sveltekit:prefetch={url && isExternal ? null : true}
sveltekit:noscroll={isExternal ? null : true}
disabled={disabled} disabled={disabled}
on:click on:click
> >

View File

@@ -1,6 +1,10 @@
<script lang="ts"> <script lang="ts">
import { cartOpen, cartAmount } from '$utils/store' import { scale } from 'svelte/transition'
import ButtonCircle from './ButtonCircle.svelte' import { quartOut } from 'svelte/easing'
import { cartOpen, cartAmount } from '$utils/stores/shop'
// Components
import Icon from '$components/atoms/Icon.svelte'
import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
const openCart = () => { const openCart = () => {
$cartOpen = true $cartOpen = true
@@ -9,10 +13,10 @@
<div class="button-cart"> <div class="button-cart">
<ButtonCircle color="purple" on:click={openCart}> <ButtonCircle color="purple" on:click={openCart}>
<img src="/images/icons/bag.svg" width={27} height={22} alt="Cart icon"> <Icon icon="bag" label="Cart icon" />
</ButtonCircle> </ButtonCircle>
{#if $cartAmount > 0} {#if $cartAmount > 0}
<span class="quantity">{$cartAmount}</span> <span class="quantity" transition:scale={{ start: 0.6, duration: 400, easing: quartOut }}>{$cartAmount}</span>
{/if} {/if}
</div> </div>

View File

@@ -0,0 +1,12 @@
<script lang="ts">
export let icon: string
export let label: string = undefined
const classes = [
$$props.class
].join(' ').trim()
</script>
<svg class={classes} aria-label={label}>
<use xlink:href="#icon-{icon}" />
</svg>

View File

@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import { cartId, cartData } from '$utils/stores/shop'
import { addToCart } from '$utils/functions/shop'
import { capitalizeFirstLetter } from '$utils/functions' import { capitalizeFirstLetter } from '$utils/functions'
// Components // Components
import Button from '$components/atoms/Button.svelte' import Button from '$components/atoms/Button.svelte'
@@ -6,6 +8,7 @@
import Carousel from '$components/organisms/Carousel.svelte' import Carousel from '$components/organisms/Carousel.svelte'
export let product: any export let product: any
export let shopProduct: any
/** /**
@@ -17,6 +20,7 @@
lastPreviewPhoto = product.photos_preview[product.photos_preview.length - 1].directus_files_id lastPreviewPhoto = product.photos_preview[product.photos_preview.length - 1].directus_files_id
} }
// Images sizes
const photosPreview = [ const photosPreview = [
{ {
sizes: { sizes: {
@@ -62,11 +66,12 @@
<div class="poster-layout__info"> <div class="poster-layout__info">
<dl> <dl>
<dt>{capitalizeFirstLetter(product.type)}</dt> <dt>{capitalizeFirstLetter(product.type)}</dt>
<dd>{product.name} 30</dd> <dd>{shopProduct.name} {shopProduct.price}</dd>
</dl> </dl>
<Button <Button
text="Add to cart" text="Add to cart"
color="pinklight" color="pinklight"
on:click={() => addToCart($cartId, shopProduct)}
/> />
</div> </div>

View File

@@ -0,0 +1,63 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
// Components
import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
import Select from '$components/molecules/Select.svelte'
export let item: any
export let index: number
const dispatch = createEventDispatcher()
const quantityLimit = 5
// When changing item quantity
const updateQuantity = ({ detail }: any) => {
dispatch('updatedQuantity', {
id: item.id,
quantity: Number(detail)
})
}
// When removing item
const removeItem = () => {
dispatch('removed', item.id)
}
</script>
<div class="cart-item shadow-small">
<div class="cart-item__left">
<img src={item.product.images[0].file.url} width={200} height={300} alt={item.product.name}>
</div>
<div class="cart-item__right">
<h3>Poster</h3>
<p>{item.product.name} {item.price_total}</p>
{#if item && item.quantity}
<Select
name="sort" id="filter_sort"
options={[...Array(item.quantity <= quantityLimit ? quantityLimit : item.quantity)].map((_, index) => {
return {
value: `${index + 1}`,
name: `${index + 1}`,
default: index === 0,
selected: index + 1 === item.quantity,
}
})}
on:change={updateQuantity}
value={String(item.quantity)}
>
<span>Quantity:</span>
</Select>
{/if}
<ButtonCircle class="remove"
size="tiny" color="gray"
on:click={removeItem}
>
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.81 1.104a.647.647 0 1 0-.914-.915L4 3.085 1.104.19a.647.647 0 1 0-.915.915L3.085 4 .19 6.896a.647.647 0 1 0 .915.915L4 4.915 6.896 7.81a.647.647 0 1 0 .915-.915L4.915 4 7.81 1.104Z" fill="#666"/>
</svg>
</ButtonCircle>
</div>
</div>

View File

@@ -1,19 +1,28 @@
<script lang="ts"> <script lang="ts">
import { getContext } from 'svelte' import { fly } from 'svelte/transition'
// Components import { quartOut } from 'svelte/easing'
import Button from '$components/atoms/Button.svelte' import { cartOpen } from '$utils/stores/shop'
import Image from '$components/atoms/Image.svelte'
const { locations, shop } = getContext('global') export let title: string
export let name: string
export let image: string
const closeNotification = () => {
// Open cart
$cartOpen = true
}
</script> </script>
<aside class="notification-cart shadow-small"> <div class="notification-cart shadow-small"
on:click={closeNotification}
transition:fly={{ y: 20, duration: 700, easing: quartOut }}
>
<div class="notification-cart__left"> <div class="notification-cart__left">
<img src="/images/issue-1.jpg" width={58} height={88} alt=""> <img src={image} width={58} height={88} alt={title}>
</div> </div>
<div class="notification-cart__right"> <div class="notification-cart__right">
<h3>Added to cart</h3> <h3>{title}</h3>
<p>Houses Of Melbourne</p> <p>{name}</p>
</div> </div>
</aside> </div>

View File

@@ -1,7 +1,11 @@
<script lang="ts"> <script lang="ts">
import { cartId } from '$utils/stores/shop'
import { addToCart } from '$utils/functions/shop'
// Components
import Button from '$components/atoms/Button.svelte' import Button from '$components/atoms/Button.svelte'
import Image from '$components/atoms/Image.svelte' import Image from '$components/atoms/Image.svelte'
export let product: any
export let location: { name: string, slug: string } export let location: { name: string, slug: string }
export let image: any export let image: any
</script> </script>
@@ -33,6 +37,7 @@
text="Add to cart" text="Add to cart"
color="pinklight" color="pinklight"
size="xsmall" size="xsmall"
on:click={() => addToCart($cartId, product)}
/> />
</div> </div>
</div> </div>

View File

@@ -1,37 +0,0 @@
<script lang="ts">
import { getContext } from 'svelte'
// Components
import Select from './Select.svelte'
import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
const { locations, shop } = getContext('global')
</script>
<aside class="poster-cart">
<div class="poster-cart__left">
<img src="/images/issue-1.jpg" alt="">
</div>
<div class="poster-cart__right">
<div class="remove">
<ButtonCircle size="tiny" color="gray">
<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.81 1.104a.647.647 0 1 0-.914-.915L4 3.085 1.104.19a.647.647 0 1 0-.915.915L3.085 4 .19 6.896a.647.647 0 1 0 .915.915L4 4.915 6.896 7.81a.647.647 0 1 0 .915-.915L4.915 4 7.81 1.104Z" fill="#666"/>
</svg>
</ButtonCircle>
</div>
<h3>Poster</h3>
<p>Houses Of Melbourne 30€</p>
<Select
name="quantity" id="filter_country"
options={[
{
value: '1',
name: '1',
default: true,
selected: true,
},
]}
/>
</div>
</aside>

View File

@@ -0,0 +1,34 @@
<script lang="ts">
import { page } from '$app/stores'
import { goto } from '$app/navigation'
import { shopLocations } from '$utils/stores/shop'
export let isOver: boolean = false
const classes = [
'shop-locationswitcher',
isOver && 'is-over',
$$props.class
].join(' ').trim()
// Quick location change
const quickLocationChange = ({ target: { value }}: any) => {
const newPath = $page.path.split('-')[0] + `-${value}`
goto(newPath, { replaceState: true, noscroll: true, keepfocus: true })
}
</script>
<dl class={classes}>
<dt class="text-label">Shop your city</dt>
<dd>
<svg width="14" height="17" viewBox="0 0 14 17" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.1 11.18a.52.52 0 0 1-.92 0L3.55 6.05a3.46 3.46 0 1 1 6.19 0L7.1 11.18Zm1.2-6.62c0-.91-.73-1.63-1.65-1.63a1.63 1.63 0 1 0 0 3.26c.92 0 1.65-.72 1.65-1.63Zm-2.88 8.18.03.05-.08 1.1 2.98.95v-3.48l.96-1.8v5.28l2.98-.95-.41-5.65-2.31.84.62-1.14 1.91-.7c.3-.09.52.16.55.46l.61 6c.04.42-.21.8-.61.93l-3.82 1.21-3.96-1.21-4.25 1.21a.54.54 0 0 1-.45-.08.4.4 0 0 1-.17-.38l.95-6.53c.04-.16.15-.3.32-.35l1.78-.57.4.77-1.59.51-.82 5.63 3.4-.97.2-2.76c.47.95.78 1.63.78 1.63Z"/>
</svg>
<select on:change={quickLocationChange}>
{#each $shopLocations as { name, slug }}
<option value={slug} selected={slug === $page.params.name}>{name}</option>
{/each}
</select>
</dd>
</dl>

View File

@@ -1,25 +1,27 @@
<script lang="ts"> <script lang="ts">
import Icon from '$components/atoms/Icon.svelte'
let isOpen: boolean = false let isOpen: boolean = false
// Links // Links
const links = [ const links = [
{ {
"icon": "globe", icon: "globe",
"alt": "Globe", iconLabel: "Globe icon",
"url": "/locations", url: "/locations",
"text": "Discover locations" text: "Discover locations"
}, },
{ {
"icon": "photos", icon: "photos",
"alt": "Photos", iconLabel: "Photos icon",
"url": "/photos", url: "/photos",
"text": "Browse all photos" text: "Browse all photos"
}, },
{ {
"icon": "bag", icon: "bag",
"alt": "Shopping bag", iconLabel: "Bag icon",
"url": "/shop", url: "/shop",
"text": "Shop the prints" text: "Shop the prints"
} }
] ]
@@ -35,10 +37,10 @@
<aside class="switcher" class:is-open={isOpen}> <aside class="switcher" class:is-open={isOpen}>
<nav class="switcher__links"> <nav class="switcher__links">
<ul> <ul>
{#each links as { icon, alt, url, text }} {#each links as { icon, iconLabel, url, text }}
<li> <li>
<a href={url} on:click={toggleSwitcher} sveltekit:prefetch> <a href={url} on:click={toggleSwitcher} sveltekit:prefetch>
<img src="/images/icons/{icon}.svg" alt={alt} class="icon" width="32" height="32" loading="lazy"> <Icon class="icon" icon={icon} label={iconLabel} />
<span>{text}</span> <span>{text}</span>
</a> </a>
</li> </li>

View File

@@ -54,6 +54,8 @@
const handleArrowClick = () => { const handleArrowClick = () => {
if (!carousel.clickAllowed()) return if (!carousel.clickAllowed()) return
// TODO: Clicking should also define arrowDirection? Can click without moving and won't change direction
// Click only if carousel if being dragged // Click only if carousel if being dragged
if (arrowDirection === 'prev') { if (arrowDirection === 'prev') {
carousel.scrollPrev() carousel.scrollPrev()

View File

@@ -1,49 +1,173 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'
import { fade, fly } from 'svelte/transition' import { fade, fly } from 'svelte/transition'
import { cartOpen } from '$utils/store'
import { quartOut } from 'svelte/easing' import { quartOut } from 'svelte/easing'
import { cartOpen, cartId, cartData, cartAmount, cartIsUpdating } from '$utils/stores/shop'
// Components // Components
import Button from '$components/atoms/Button.svelte' import Button from '$components/atoms/Button.svelte'
import PosterCart from '$components/molecules/PosterCart.svelte' import Icon from '$components/atoms/Icon.svelte'
import CartItem from '$components/molecules/CartItem.svelte'
import ShopLocationSwitcher from '$components/molecules/ShopLocationSwitcher.svelte'
onMount(async () => {
// Cart already exists
if ($cartId && $cartId !== 'null') {
// Fetch stored cart
const existantCart = await fetch('/api/swell', {
method: 'POST',
body: JSON.stringify({
action: 'fetchCart',
cartId: $cartId
})
})
if (existantCart.ok) {
const cart = await existantCart.json()
$cartId = cart.id
$cartData = cart
// console.log('Fetched existant cart:', $cartId, $cartData)
}
}
// Cart doesn't exists
else {
// Create a new cart and store it
const newCart = await fetch('/api/swell', {
method: 'POST',
body: JSON.stringify({
action: 'createCart'
})
})
if (newCart.ok) {
const cart = await newCart.json()
$cartId = cart.id
$cartData = cart
// console.log('Created new cart:', localStorage.getItem('cartId'))
}
}
})
// Closing the cart // Closing the cart
const handleCloseCart = () => { const handleCloseCart = () => {
$cartOpen = false $cartOpen = false
} }
// Item quantity changed
const changedQuantity = async ({ detail: { id, quantity } }) => {
// Cart is now updating
$cartIsUpdating = true
// Get updated cart
const updatedCart = await fetch('/api/swell', {
method: 'POST',
body: JSON.stringify({
action: 'updateCartItem',
cartId: $cartId,
productId: id,
quantity,
})
})
if (updatedCart.ok) {
const cart = await updatedCart.json()
$cartData = cart
// Cart is updated
$cartIsUpdating = false
}
}
// Item removed
const removedItem = async ({ detail: id }) => {
// Cart is now updating
$cartIsUpdating = true
// Remove item from cart
const updatedCart = await fetch('/api/swell', {
method: 'POST',
body: JSON.stringify({
action: 'removeCartItem',
cartId: $cartId,
productId: id,
})
})
if (updatedCart.ok) {
const cart = await updatedCart.json()
$cartData = cart
// Cart is updated
$cartIsUpdating = false
}
}
</script> </script>
<aside class="cart" {#if $cartOpen}
transition:fly={{ x: 48, duration: 600, easing: quartOut }} <div class="cart-switcher"
> in:fly={{ y: -24, duration: 1000, easing: quartOut }}
<header class="cart__heading"> out:fly={{ y: -24, duration: 1000, easing: quartOut }}
<h2>Cart</h2> >
<button class="text-label" on:click={handleCloseCart}>Close</button> <ShopLocationSwitcher isOver={true} />
</header>
<div class="cart__content">
<PosterCart />
<PosterCart />
</div> </div>
<footer class="cart__total"> <aside class="cart shadow-box-dark"
<div class="cart__total--sum"> class:is-updating={$cartIsUpdating}
<h3>Total</h3> transition:fly={{ x: 48, duration: 600, easing: quartOut }}
<span>3 articles</span> >
<p>90€</p> <header class="cart__heading">
</div> <h2>Cart</h2>
<div class="cart__total--checkout"> <button class="text-label" on:click={handleCloseCart}>Close</button>
<p>Shipping will be calculated from the delivery address during the checkout process</p> </header>
<Button
text="Checkout"
color="pink"
size="small"
/>
</div>
</footer>
</aside>
<div class="cart-overlay" <div class="cart__content">
transition:fade={{ duration: 600, easing: quartOut }} {#if $cartAmount > 0}
on:click={handleCloseCart} {#each $cartData.items as item, index}
/> <CartItem {item} {index}
on:updatedQuantity={changedQuantity}
on:removed={removedItem}
/>
{/each}
{:else}
<div class="cart__empty shadow-small">
<div class="icon">
<Icon icon="bag" label="Shopping bag icon" />
</div>
<p>Your cart is empty</p>
</div>
{/if}
{#if !$cartData || $cartIsUpdating}
<div class="cart__update">
<p>Updating…</p>
</div>
{/if}
</div>
<footer class="cart__total">
<div class="cart__total--sum">
<h3>Total</h3>
{#if $cartData}
<span>{$cartAmount} item{$cartAmount > 1 ? 's' : ''}</span>
<p>{$cartData.sub_total ? $cartData.sub_total : 0}</p>
{:else}
<span>0 item</span>
<p>0€</p>
{/if}
</div>
<div class="cart__total--checkout">
<p>Shipping will be calculated from the delivery address during the checkout process</p>
{#if $cartData && $cartAmount > 0 && $cartData.checkout_url}
<div transition:fly={{ y: 8, duration: 600, easing: quartOut }}>
<Button
url={$cartData && $cartData.checkout_url}
text="Checkout"
color="pink"
size="small"
/>
</div>
{/if}
</div>
</footer>
</aside>
<div class="cart-overlay"
transition:fade={{ duration: 600, easing: quartOut }}
on:click={handleCloseCart}
/>
{/if}

View File

@@ -29,7 +29,7 @@
<h3 class="title-medium">{shop.module_title}</h3> <h3 class="title-medium">{shop.module_title}</h3>
<p class="text-small">{shop.module_text}</p> <p class="text-small">{shop.module_text}</p>
{#if shop.enabled} {#if shop.enabled}
<Button url="/shop" text="Shop" color="lightpink" /> <Button url="/shop" text="Shop" color="pinklight" />
{/if} {/if}
<p class="detail"> <p class="detail">
Posters available for {locationsWithPoster.join(', ').replace(/,(?!.*,)/gmi, ' and')}. Posters available for {locationsWithPoster.join(', ').replace(/,(?!.*,)/gmi, ' and')}.

View File

@@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/stores' import { page } from '$app/stores'
import { getAssetUrlKey } from '$utils/helpers'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import advancedFormat from 'dayjs/plugin/advancedFormat.js' import advancedFormat from 'dayjs/plugin/advancedFormat.js'
import { getAssetUrlKey } from '$utils/helpers'
// Components // Components
import Metas from '$components/Metas.svelte' import Metas from '$components/Metas.svelte'
import Image from '$components/atoms/Image.svelte' import Image from '$components/atoms/Image.svelte'
import Icon from '$components/atoms/Icon.svelte'
import IconArrow from '$components/atoms/IconArrow.svelte' import IconArrow from '$components/atoms/IconArrow.svelte'
import ButtonCircle from '$components/atoms/ButtonCircle.svelte' import ButtonCircle from '$components/atoms/ButtonCircle.svelte'
@@ -20,13 +21,12 @@
$: isLast = currentIndex === totalPhotos - 1 $: isLast = currentIndex === totalPhotos - 1
$: isFirst = currentIndex === 0 $: isFirst = currentIndex === 0
// Define current photo // Define current photo
// $: currentPhoto = photos.find((photo: any) => photo.slug === $page.params.photo)
$: currentPhoto = photos[photos.findIndex((photo: any) => photo.slug === $page.params.photo)] $: currentPhoto = photos[photos.findIndex((photo: any) => photo.slug === $page.params.photo)]
// Reactive photos showns // Reactive photos
$: shownPhotos = [ $: displayedPhotos = [
...photos.filter((photo: any, index: number) => { ...photos.filter((photo: any, index: number) => {
// Grab the 4 prev and next photos depending the index // Grab the 4 prev and next photos depending on the index
console.log(index) // console.log(index)
// console.log(index >= currentIndex - 4 && index < currentIndex + 4) // console.log(index >= currentIndex - 4 && index < currentIndex + 4)
}) })
] ]
@@ -120,7 +120,7 @@
<h1 class="title-medium">{currentPhoto.title}</h1> <h1 class="title-medium">{currentPhoto.title}</h1>
<div class="detail text-date"> <div class="detail text-date">
<img src="/images/icons/map-pin.svg" width={16} height={18} alt="Map icon"><span>{location.name}, {location.country.name}</span> <span class="sep">&middot;</span> <time datetime={dayjs(currentPhoto.date_taken).format('YYYY-MM-DD')}>{dayjs(currentPhoto.date_taken).format('MMMM, Do YYYY')}</time> <Icon class="icon" icon="map-pin" label="Map pin" /><span>{location.name}, {location.country.name}</span> <span class="sep">&middot;</span> <time datetime={dayjs(currentPhoto.date_taken).format('YYYY-MM-DD')}>{dayjs(currentPhoto.date_taken).format('MMMM, Do YYYY')}</time>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -143,7 +143,7 @@
<Button url="/locations" text="Change location" class="shadow-small"> <Button url="/locations" text="Change location" class="shadow-small">
<IconEarth /> <IconEarth />
</Button> </Button>
<Button url="/shop" text="Buy the poster" color="lightpink" class="shadow-small"> <Button url="/shop/poster-{location.slug}" text="Buy the poster" color="pinklight" class="shadow-small">
<!-- <IconEarth /> --> <!-- <IconEarth /> -->
</Button> </Button>
</div> </div>

View File

@@ -4,6 +4,7 @@
import { setContext } from 'svelte' import { setContext } from 'svelte'
import '$utils/polyfills' import '$utils/polyfills'
// Components // Components
import SVGSprite from '$components/SVGSprite.svelte'
import Switcher from '$components/molecules/Switcher.svelte' import Switcher from '$components/molecules/Switcher.svelte'
import Footer from '$components/organisms/Footer.svelte' import Footer from '$components/organisms/Footer.svelte'
@@ -25,6 +26,8 @@
<Footer /> <Footer />
{/if} {/if}
<SVGSprite />
<script context="module" lang="ts"> <script context="module" lang="ts">
import { fetchAPI } from '$utils/api' import { fetchAPI } from '$utils/api'

74
src/routes/api/swell.ts Normal file
View File

@@ -0,0 +1,74 @@
import {
getProducts,
getProduct,
createCart,
fetchCart,
addToCart,
updateCartItem,
removeCartItem,
} from '$utils/functions/swell'
// Block GET requests
export async function get ({ body, query }) {
return {
status: 403,
body: 'nope!'
}
}
/**
* POST request
*/
export async function post ({ headers, query, body, params, ...rest }) {
try {
const bodyParsed = JSON.parse(Buffer.from(body).toString())
const { action, cartId, productId } = bodyParsed
let result = {}
if (bodyParsed) {
switch (action) {
case 'getProducts': {
result = await getProducts(bodyParsed.category)
break
}
case 'getProduct': {
result = await getProduct(productId)
break
}
case 'createCart': {
result = await createCart()
break
}
case 'fetchCart': {
result = await fetchCart(cartId)
break
}
case 'addToCart': {
result = await addToCart(cartId, productId, bodyParsed.quantity)
break
}
case 'updateCartItem': {
result = await updateCartItem(cartId, productId, bodyParsed.quantity)
break
}
case 'removeCartItem': {
result = await removeCartItem(cartId, productId)
break
}
default: break
}
}
return {
status: 200,
body: result,
}
}
catch (error) {
return {
status: error.status || 500,
body: error.message || error.text || `Can't fetch query`
}
}
}

View File

@@ -58,19 +58,19 @@
<div class="cards"> <div class="cards">
<BoxCTA <BoxCTA
url="{path}#locations" url="{path}#locations"
icon="/images/icons/globe.svg" icon="globe"
label="Discover locations" label="Discover locations"
alt="Globe" alt="Globe"
/> />
<BoxCTA <BoxCTA
url="/photos" url="/photos"
icon="/images/icons/photos.svg" icon="photos"
label="Browse all photos" label="Browse all photos"
alt="Photos" alt="Photos"
/> />
<BoxCTA <BoxCTA
url="/shop" url="/shop"
icon="/images/icons/bag.svg" icon="bag"
label="Shop our products" label="Shop our products"
alt="Shopping bag" alt="Shopping bag"
/> />

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import PosterLayout from '$components/layouts/PosterLayout.svelte'
export let product: any
export let shopProduct: any
</script>
<PosterLayout
product={product}
shopProduct={shopProduct}
/>
<script context="module" lang="ts">
export async function load ({ page, fetch, session, stuff }) {
// Get content from stuff
return {
props: {
product: stuff.product,
shopProduct: stuff.shopProduct,
}
}
}
</script>

View File

@@ -1,31 +1,41 @@
<script lang="ts"> <script lang="ts">
import { cartOpen } from '$utils/store'
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { shopLocations, cartOpen, cartNotifications } from '$utils/stores/shop'
// Components // Components
import Metas from '$components/Metas.svelte' import Metas from '$components/Metas.svelte'
import SiteTitle from '$components/atoms/SiteTitle.svelte' import SiteTitle from '$components/atoms/SiteTitle.svelte'
import Image from '$components/atoms/Image.svelte' import Image from '$components/atoms/Image.svelte'
import ButtonCart from '$components/atoms/ButtonCart.svelte' import ButtonCart from '$components/atoms/ButtonCart.svelte'
import PosterLayout from '$components/layouts/PosterLayout.svelte'
import Poster from '$components/molecules/Poster.svelte' import Poster from '$components/molecules/Poster.svelte'
import NotificationCart from '$components/molecules/NotificationCart.svelte' import NotificationCart from '$components/molecules/NotificationCart.svelte'
import EmailForm from '$components/molecules/EmailForm.svelte' import EmailForm from '$components/molecules/EmailForm.svelte'
import ShopLocationSwitcher from '$components/molecules/ShopLocationSwitcher.svelte'
import Cart from '$components/organisms/Cart.svelte' import Cart from '$components/organisms/Cart.svelte'
export let shop: any export let shop: any
export let locations: any export let locations: any
export let posters: any export let posters: any
export let product: any export let product: any
export let shopProducts: any
let navEl: HTMLElement, introEl: HTMLElement let introEl: HTMLElement
let navObserver: IntersectionObserver let navObserver: IntersectionObserver
let scrolledPastIntro = false
// Locations with an existing poster product
$shopLocations = locations.filter(({ slug }: any) => {
if (posters.find((poster: any) => poster.location.slug === slug)) {
return true
}
})
onMount(async () => { onMount(async () => {
// Reveal the nav past the Intro // Reveal the nav past the Intro
navObserver = new IntersectionObserver(entries => { navObserver = new IntersectionObserver(entries => {
entries.forEach(entry => { entries.forEach(entry => {
navEl.classList.toggle('is-visible', !entry.isIntersecting) scrolledPastIntro = !entry.isIntersecting
}) })
}, { }, {
threshold: 0, threshold: 0,
@@ -48,38 +58,42 @@
/> />
<main class="shop-page"> <main class="shop-page">
{#if $cartOpen} <Cart />
<Cart />
{/if}
<section class="shop-page__intro">
<section class="shop-page__intro" bind:this={introEl}> <section class="shop-page__intro" bind:this={introEl}>
<a href="/" class="back"> <div class="top container">
Back to Houses Of <a href="/" class="back">
</a> <svg width="5" height="8" viewBox="0 0 5 8" fill="none" xmlns="http://www.w3.org/2000/svg">
<span class="shop-title">Shop</span> <path d="M4 1 1 4l3 3" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>Back to site</span>
</a>
<span class="shop-title">Shop</span>
</div>
<SiteTitle <SiteTitle
variant="inline" variant="inline"
/> />
<header class="shop-page__header"> <nav class="shop-page__nav">
<div class="container"> <div class="container">
<p class="text-label">Shop your city</p> <p class="text-label">Shop your city</p>
<nav> <nav>
<ul> <ul>
{#each locations as { name, slug }} {#each $shopLocations as { name, slug }}
{#if posters.find(poster => poster.location.slug === slug )} <li class:is-active={slug === product.location.slug}>
<li class:is-active={slug === product.location.slug}> <a href="/shop/poster-{slug}" sveltekit:noscroll sveltekit:prefetch>
<a href="/shop/poster-{slug}">{name}</a> {name}
</li> </a>
{/if} </li>
{/each} {/each}
</ul> </ul>
</nav> </nav>
<ButtonCart /> <ButtonCart />
</div> </div>
</header> </nav>
<Image <Image
id={shop.page_heroimage.id} id={shop.page_heroimage.id}
@@ -93,55 +107,61 @@
/> />
</section> </section>
<nav class="shop-location" bind:this={navEl}> <nav class="shop-location"
<dl class="shop-location__left"> class:is-visible={scrolledPastIntro}
<dt class="text-label">shop your city</dt> class:is-overlaid={$cartOpen}
<dd> >
<img src="/images/icons/pin.svg" alt=""> <ShopLocationSwitcher />
<select name="" id="">
<option value="melbourne">Melbourne</option>
</select>
</dd>
</dl>
<ButtonCart /> <ButtonCart />
<div class="shop-location__notifications"> <div class="shop-location__notifications">
<NotificationCart /> {#each $cartNotifications as { id, title, name, image } (id)}
<NotificationCart
title={title}
name={name}
image={image}
/>
{/each}
</div> </div>
</nav> </nav>
<section class="shop-page__about grid"> <section class="shop-page__about grid">
<p class="description text-normal">{shop.about}</p> <div class="description grid text-normal">
</section> <div class="description__inner">
{@html shop.about}
<PosterLayout {product} /> </div>
<section class="shop-page__posters grid">
<h3>View all of our available posters</h3>
<div class="set">
{#each posters as { location, photos_product }}
<Poster
location={location}
image={photos_product.length && photos_product[1].directus_files_id}
/>
{/each}
</div>
<div class="subscribe">
<p>Subscribe to be notified when new posters become available</p>
<EmailForm />
</div> </div>
</section> </section>
<slot />
{#if posters}
<section class="shop-page__posters grid">
<h3>View all of our available posters</h3>
<div class="set">
{#each posters as { location, photos_product }}
<Poster
location={location}
image={photos_product.length && photos_product[1].directus_files_id}
product={shopProducts.find(item => item.slug.includes(location.slug))}
/>
{/each}
</div>
<div class="subscribe">
<p>Subscribe to be notified when new posters become available</p>
<EmailForm />
</div>
</section>
{/if}
</main> </main>
<slot />
<script context="module" lang="ts"> <script context="module" lang="ts">
import { fetchAPI } from '$utils/api' import { fetchAPI } from '$utils/api'
import { getRandomElement } from '$utils/functions' import { getRandomElement } from '$utils/functions'
export async function load ({ page, fetch, session, stuff }) { export async function load ({ page, fetch, session, stuff }) {
// Get content from API
const res = await fetchAPI(` const res = await fetchAPI(`
query { query {
shop { shop {
@@ -166,6 +186,7 @@
name name
slug slug
} }
product_id
photos_product { photos_product {
directus_files_id { directus_files_id {
id id
@@ -184,25 +205,57 @@
const { data } = res const { data } = res
/** /**
* Define product * Define product
*/ */
let product: any const productAPI = (!page.params.type && !page.params.name)
if (!page.params.type && !page.params.name) {
// Get a random product // Get a random product
product = data.posters[getRandomElement(data.posters)] ? data.posters[getRandomElement(data.posters)]
} else {
// Get the current product from slug // Get the current product from slug
product = data.posters.find(({ location }: any) => location.slug === page.params.name) : data.posters.find(({ location }: any) => location.slug === page.params.name)
/**
* Get products data from Swell
*/
let shopProducts: any
let shopProduct: any
const shopProductRes = await fetch('/api/swell', {
method: 'POST',
body: JSON.stringify({
action: 'getProducts',
category: 'posters',
})
})
if (shopProductRes.ok) {
// Set all products
const { results } = await shopProductRes.json()
if (results) {
shopProducts = results
}
// Define current product
const product = results.find((result: any) => result.slug.includes(productAPI.location.slug))
if (product) {
shopProduct = product
}
} }
return { return {
props: { props: {
shop: data.shop, shop: data.shop,
locations: data.location, locations: data.location,
posters: data.posters, posters: data.posters,
product, product: productAPI,
shopProducts,
shopProduct,
},
stuff: {
product: productAPI,
shopProduct,
} }
} }
} }

View File

@@ -0,0 +1,23 @@
<script lang="ts">
import PosterLayout from '$components/layouts/PosterLayout.svelte'
export let product: any
export let shopProduct: any
</script>
<PosterLayout
product={product}
shopProduct={shopProduct}
/>
<script context="module" lang="ts">
export async function load ({ page, fetch, session, stuff }) {
// Get content from stuff
return {
props: {
product: stuff.product,
shopProduct: stuff.shopProduct,
}
}
}
</script>

View File

@@ -21,6 +21,7 @@
.icon { .icon {
width: 36px; width: 36px;
height: 36px; height: 36px;
color: #fff;
flex-shrink: 0; flex-shrink: 0;
transition: transform 0.6s var(--ease-quart); transition: transform 0.6s var(--ease-quart);
@@ -30,11 +31,10 @@
height: 48px; height: 48px;
} }
img { svg {
display: block; display: block;
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: contain;
} }
} }
span { span {

View File

@@ -9,7 +9,8 @@
} }
// Icon // Icon
img { svg {
color: #fff;
display: block; display: block;
width: 27px; width: 27px;
height: 27px; height: 27px;

View File

@@ -12,7 +12,7 @@
text-decoration: none; text-decoration: none;
transition: background-color 0.55s var(--ease-quart), color 0.55s var(--ease-quart); transition: background-color 0.55s var(--ease-quart), color 0.55s var(--ease-quart);
@include bp (sm) { @include bp (md) {
height: 48px; height: 48px;
padding: 1px 24px 0; padding: 1px 24px 0;
font-size: rem(18px); font-size: rem(18px);
@@ -44,7 +44,7 @@
height: 32px; height: 32px;
padding: 0 6px; padding: 0 6px;
@include bp (sm) { @include bp (md) {
height: 32px; height: 32px;
padding: 0 16px; padding: 0 16px;
font-size: rem(14px); font-size: rem(14px);
@@ -56,7 +56,7 @@
height: 40px; height: 40px;
padding: 0 16px; padding: 0 16px;
@include bp (sm) { @include bp (md) {
height: 40px; height: 40px;
padding: 0 16px; padding: 0 16px;
} }
@@ -67,7 +67,7 @@
height: 56px; height: 56px;
font-size: rem(16px); font-size: rem(16px);
@include bp (sm) { @include bp (md) {
height: 72px; height: 72px;
height: 72px; height: 72px;
padding: 0 40px; padding: 0 40px;
@@ -108,7 +108,6 @@
} }
.text-split__line { .text-split__line {
&:last-child { &:last-child {
color: $color-text;
color: $color-primary-dark; color: $color-primary-dark;
} }
} }

View File

@@ -75,7 +75,7 @@
@include bp (sm) { @include bp (sm) {
font-size: 0.333em; font-size: 0.333em;
margin: 0 16px 0 20px; margin: 0 10px 0 20px;
} }
} }
} }

View File

@@ -14,8 +14,7 @@
@include bp (sm) { @include bp (sm) {
grid-column: 2 / span calc(var(--columns) - 1); grid-column: 2 / span calc(var(--columns) - 1);
text-align: center; margin-bottom: 88px;
margin-bottom: 48px;
} }
} }

View File

@@ -0,0 +1,122 @@
// Cart item
.cart-item {
position: relative;
display: flex;
align-items: center;
margin-bottom: 24px;
background: #fff;
color: $color-gray;
border-radius: 6px;
overflow: hidden;
transition: opacity 0.3s var(--ease-quart);
&:last-child {
margin-bottom: 0;
}
// Left Image
&__left {
width: 100px;
height: 150px;
margin-right: 20px;
@include bp (sm) {
width: 124px;
height: 180px;
margin-right: 32px;
}
img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 6px 0 0 6px;
}
}
// Details
&__right {
display: flex;
flex-direction: column;
// Poster Title
h3 {
font-family: $font-serif;
color: $color-secondary;
font-size: rem(20px);
@include bp (sm) {
font-size: rem(28px);
}
}
// Text
p {
max-width: 124px;
margin: 8px 0 20px;
font-size: rem(12px);
line-height: 1.4;
@include bp (sm) {
font-size: rem(13px);
}
}
// Select
.select {
position: relative;
display: flex;
align-items: center;
height: 28px;
margin-right: auto;
padding: 0 12px;
border: 1px solid rgba($color-lightgray, 0.3);
border-radius: 100vh;
cursor: pointer;
transition: border-color 0.4s var(--ease-quart);
span {
display: block;
font-size: rem(12px);
color: $color-text;
& + span {
margin-left: 4px;
}
}
select {
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0;
cursor: pointer;
text-align: center;
}
&:after {
content: "";
display: block;
width: 8px;
height: 5px;
margin-left: 4px;
color: red;
background: url('data:image/svg+xml; utf8, <svg width="8" height="5" viewBox="0 0 8 5" fill="none" xmlns="http://www.w3.org/2000/svg">\
<path d="m1 1 3 3 3-3" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>\
</svg>');
}
// Hover
&:hover {
border-color: $color-secondary-light;
}
}
// Remove Icon
.remove {
position: absolute;
top: 16px;
right: 16px;
}
}
}

View File

@@ -0,0 +1,43 @@
.shop-locationswitcher {
dt {
color: $color-primary;
font-weight: 400;
line-height: 1;
font-size: rem(12px);
}
dd {
color: $color-secondary;
svg {
display: inline-block;
width: 14px;
height: 14px;
margin-right: 4px;
@include bp (sm) {
width: 18px;
height: 18px;
}
}
select {
background: none;
border: none;
font-size: rem(18px);
color: $color-secondary;
font-family: $font-serif;
cursor: pointer;
appearance: none;
@include bp (sm) {
font-size: rem(24px);
}
}
}
// Overlaid
&.is-over {
dt {
color: #fff;
}
}
}

View File

@@ -51,6 +51,7 @@
height: 32px; height: 32px;
object-fit: contain; object-fit: contain;
margin-right: 16px; margin-right: 16px;
color: $color-secondary-light;
} }
} }

View File

@@ -1,12 +1,13 @@
.cart { .cart {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: auto;
padding: 20px 20px 24px;
background-color: $color-cream; background-color: $color-cream;
border-radius: 8px; border-radius: 8px;
padding: 20px 20px 24px;
@include bp (sm) { @include bp (sm) {
padding: 24px 32px; padding: 24px 32px 32px;
} }
// Heading // Heading
@@ -14,8 +15,8 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding-bottom: 8px;
margin-bottom: 24px; margin-bottom: 24px;
padding-bottom: 8px;
border-bottom: 1px solid #E1D0C0; border-bottom: 1px solid #E1D0C0;
@include bp (sm) { @include bp (sm) {
@@ -33,98 +34,45 @@
font-size: rem(48px); font-size: rem(48px);
} }
} }
// Close // Close button
a { button {
display: block;
padding: 8px 12px;
margin-top: 8px;
margin-right: -12px;
color: $color-gray; color: $color-gray;
text-decoration: none; text-decoration: none;
transition: color 0.4s;
&:hover {
color: $color-secondary-bright;
}
} }
} }
// Poster Cart // Content
.poster-cart { &__content {
position: relative; position: relative;
display: flex; flex: 1;
background-color: #fff; margin-bottom: 32px;
color: $color-gray;
margin-bottom: 24px;
border-radius: 6px;
align-items: center;
&:last-child {
margin-bottom: 0;
}
// Left Image
&__left {
margin-right: 20px;
width: 100px;
height: 150px;
@include bp (sm) {
margin-right: 32px;
width: 124px;
height: 180px;
}
img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 6px 0 0 6px;
}
}
&__right {
// Remove Icon
.remove {
position: absolute;
top: 16px;
right: 16px;
}
// Poster Title
h3 {
font-family: $font-serif;
color: $color-secondary;
font-size: rem(20px);
@include bp (sm) {
font-size: rem(28px);
}
}
// Text
p {
font-size: rem(12px);
line-height: 1.4;
max-width: 124px;
margin: 8px 0 20px;
@include bp (sm) {
font-size: rem(13px);
}
}
}
} }
// Total // Total
&__total { &__total {
color: $color-gray;
margin-bottom: 10px;
margin-top: auto; margin-top: auto;
padding-top: 20px; color: $color-gray;
// Sum // Sum
&--sum { &--sum {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
margin-bottom: 16px;
padding-bottom: 10px; padding-bottom: 10px;
margin-bottom: 17px;
border-bottom: 1px solid #E1D0C0; border-bottom: 1px solid #E1D0C0;
@include bp (sm) { @include bp (sm) {
padding-bottom: 12px;
margin-bottom: 32px; margin-bottom: 32px;
padding-bottom: 12px;
} }
h3 { h3 {
@@ -137,18 +85,18 @@
} }
} }
span { span {
font-size: rem(12px);
margin-left: 20px; margin-left: 20px;
font-size: rem(12px);
@include bp (sm) { @include bp (sm) {
font-size: rem(13px); font-size: rem(13px);
} }
} }
p { p {
margin-left: auto;
color: $color-secondary; color: $color-secondary;
font-family: $font-serif; font-family: $font-serif;
font-size: rem(26px); font-size: rem(26px);
margin-left: auto;
@include bp (sm) { @include bp (sm) {
font-size: rem(32px); font-size: rem(32px);
@@ -161,21 +109,107 @@
display: flex; display: flex;
p { p {
max-width: 180px;
margin-right: auto;
font-size: rem(11px); font-size: rem(11px);
line-height: 1.5; line-height: 1.5;
color: $color-gray; color: $color-gray;
max-width: 180px;
margin-right: auto;
@include bp (sm) { @include bp (sm) {
max-width: 304px;
font-size: rem(12px); font-size: rem(12px);
line-height: 1.6; line-height: 1.6;
max-width: 190px;
} }
} }
} }
} }
// Empty content
&__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 24px;
font-size: rem(16px);
font-weight: 200;
background: #fff;
color: $color-gray;
border-radius: 6px;
@include bp (sm) {
font-size: rem(20px);
}
// Icon
.icon {
display: flex;
align-items: center;
justify-content: center;
width: 3.6em;
height: 3.6em;
margin-bottom: 16px;
color: #FF6C89;
background: $color-cream;
border-radius: 100%;
svg {
display: block;
width: auto;
height: 50%;
object-fit: contain;
}
}
}
// Updating message
&__update {
position: absolute;
z-index: -1;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-size: rem(20px);
color: $color-secondary;
transition: opacity 0.6s var(--ease-quart), transform 0.6s var(--ease-quart);
opacity: 0;
transform: translate3d(0, -8px, 0);
}
// Updating state
&.is-updating {
.cart-item {
opacity: 0.1;
pointer-events: none;
user-select: none;
}
.cart__update {
opacity: 1;
transform: translate3d(0,0,0);
}
}
// Location switcher
&-switcher {
position: fixed;
z-index: 100;
top: 20px;
left: 20px;
will-change: transform, opacity;
@include bp (sm) {
top: 32px;
left: 32px;
}
}
// Overlay // Overlay
&-overlay { &-overlay {
position: fixed; position: fixed;

View File

@@ -1,12 +1,62 @@
.shop-page { .shop-page {
position: relative; position: relative;
// Sections
@import "shop/intro";
@import "shop/about";
@import "shop/posters";
// Nav
.shop-location {
--inset: 20px;
display: flex;
position: fixed;
z-index: 20;
top: var(--inset);
left: var(--inset);
right: var(--inset);
justify-content: space-between;
transform: translate3d(0, -88px, 0);
transition: transform 1s var(--ease-quart);
transition-delay: 100ms;
@include bp (sm) {
--inset: 32px;
}
// Notifications
&__notifications {
position: absolute;
z-index: 100;
top: 72px;
right: 0;
opacity: 0;
transition: opacity 0.7s var(--ease-quart);
pointer-events: none;
& > * {
&:not(:last-child) {
margin-bottom: 8px;
}
}
}
// Visible state
&.is-visible {
transform: translate3d(0,0,0);
.shop-location__notifications {
opacity: 1;
pointer-events: auto;
}
}
}
// Cart // Cart
.cart { .cart {
// display: flex;
// display: none;
position: fixed; position: fixed;
z-index: 100; z-index: 101;
top: 72px; top: 72px;
right: 0; right: 0;
width: 100%; width: 100%;
@@ -20,406 +70,4 @@
max-height: 1000px; max-height: 1000px;
} }
} }
// Nav
.shop-location {
display: flex;
position: fixed;
z-index: 20;
top: 18px;
left: 20px;
right: 20px;
justify-content: space-between;
transform: translate3d(0, -96px, 0);
transition: transform 1s var(--ease-quart);
transition-delay: 100ms;
@include bp (sm) {
top: 32px;
left: 32px;
right: 32px;
}
// Visible state
&.is-visible {
transform: translate3d(0,0,0);
}
// Left
&__left {
dt {
color: $color-primary;
font-weight: 400;
line-height: 1;
font-size: rem(12px);
color: #fff;
}
dd {
img {
display: none;
@include bp (sm) {
display: inline-block;
width: 14px;
height: 14px;
margin-right: 8px;
}
}
select {
background: none;
border: none;
font-size: rem(18px);
color: $color-secondary;
font-family: $font-serif;
appearance: none;
line-height: 1;
@include bp (sm) {
font-size: rem(24px);
}
option {
font-size: rem(16px);
}
}
}
}
// Notifications
&__notifications {
position: absolute;
z-index: 100;
top: 72px;
right: 0;
& > * {
&:not(:last-child) {
margin-bottom: 8px;
}
}
}
}
// Intro
&__intro {
position: relative;
z-index: 30;
height: 100vh;
min-height: 800px;
overflow: hidden;
// Top Menu
// Back
.back {
position: absolute;
z-index: 2;
left: 32px;
top: 32px;
color: #fff;
text-decoration: none;
font-size: rem(14px);
color: $color-cream;
max-width: 76px;
@include bp (sm) {
max-width: none;
font-size: rem(18px);
}
}
// Shop
.shop-title {
position: absolute;
z-index: 2;
top: 32px;
left: 50%;
transform: translateX(-50%);
font-size: rem(14px);
color: $color-cream;
@include bp (sm) {
font-size: rem(18px);
}
}
// Site Title
h1 {
position: absolute;
z-index: 2;
top: 50%;
left: 50%;
width: 100%;
transform: translate3d(-50%, -50%, 0);
font-size: clamp(#{rem(88px)}, 10vw, #{rem(140px)});
text-align: center;
span, strong {
color: #fff;
}
}
// Background Image
picture {
position: relative;
display: flex;
align-items: flex-end;
width: 100%;
height: 100%;
background-color: $color-primary-darker;
pointer-events: none;
user-select: none;
img {
opacity: 0.55;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
// Gradient
&:before {
display: block;
content: "";
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg, rgba(45, 4, 88, 0) 84.2%, #1E0538 100%);
}
}
// Navigation
header {
position: absolute;
z-index: 20;
bottom: 0;
width: 100%;
@include bp (md) {
height: 80px;
}
.container {
@include bp (md) {
display: grid;
grid-template-columns: 15% auto 15%;
align-items: flex-end;
gap: 16px;
height: 100%;
padding: 0 32px 32px;
}
}
// Shop
p {
margin-right: auto;
}
// Navigation
nav {
ul {
display: flex;
align-items: center;
justify-content: center;
}
li {
display: block;
}
a {
text-decoration: none;
font-size: rem(22px);
font-family: $font-serif;
color: $color-tertiary;
margin: 0 10px;
transition: color 0.3s;
&:hover {
color: $color-secondary;
}
@include bp (sm) {
font-size: rem(24px);
margin: 0 12px;
}
}
// Active
.is-active {
a {
color: $color-secondary;
}
}
}
}
}
// About
&__about {
display: flex;
background-color: $color-cream;
color: $color-text;
padding: 100px 0 56px;
@include bp (sm) {
display: grid;
padding: 120px 0;
}
.description {
font-weight: 300;
max-width: 450px;
margin-left: 20px;
padding-right: 20px;
font-size: rem(18px);
white-space: pre-line;
@include bp (sm) {
grid-column: 3 / span 12;
max-width: 560px;
margin-left: 0;
padding: 0;
font-size: rem(22px);
}
}
}
// Posters
&__posters {
background-color: $color-primary-darker;
padding: 0 20px 72px;
border-bottom: solid 1px $color-primary-tertiary20 ;
@include bp (sm) {
padding: 120px 0;
}
// Title
h3 {
grid-column: 2 / span 6;
font-family: $font-serif;
color: $color-cream;
font-size: rem(32px);
line-height: 1.1;
text-align: center;
margin-bottom: 24px;
@include bp (sm) {
grid-column: 3 / span 14;
font-size: rem(48px);
text-align: left;
max-width: 360px;
margin-bottom: 0;
}
}
// Posters Set
.set {
--gap: 24px;
grid-column: 1 / span 8;
display: block;
@include bp (sm) {
grid-column: 2 / span 22;
}
@include bp (mob-lg) {
--gap: 32px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--gap);
}
@include bp (md) {
grid-template-columns: repeat(3, 1fr);
}
@include bp (sd) {
--gap: 48px;
grid-template-columns: repeat(4, 1fr);
}
// Poster
.poster {
margin: 0 auto auto;
// 2 columns
&:nth-child(2n + 1) {
@include bp (sm, max) {
margin-top: calc(var(--gap) * 2);
}
}
// 3 columns
&:nth-child(3n + 1) {
@include bp (sd, max) {
margin-top: calc(var(--gap) * 2);
}
}
&:nth-child(3n + 2) {
@include bp (sd, max) {
margin-top: var(--gap);
}
}
// 4 columns
&:nth-child(4n + 1) {
@include bp (sd) {
margin-top: 64px * 3;
}
}
&:nth-child(4n + 2) {
@include bp (sd) {
margin-top: 64px * 2;
}
}
&:nth-child(4n + 3) {
@include bp (sd) {
margin-top: 64px;
}
}
}
}
// Subscribe
.subscribe {
grid-column: 1 / span var(--columns);
max-width: 380px;
margin: 72px auto 0;
text-align: center;
@include bp (sm) {
grid-column: 14 / span 8;
margin-top: 0;
text-align: left;
}
p {
font-size: rem(18px);
color: $color-cream;
font-weight: 300;
line-height: 1.5;
margin-bottom: 24px;
@include bp (sm) {
font-size: rem(22px);
margin-bottom: 30px;
}
}
.newsletter-form {
padding: 0;
margin: 0 auto;
@include bp (sm) {
margin: 0;
}
}
.newsletter-form__bottom {
display: none;
}
}
}
} }

View File

@@ -185,8 +185,10 @@
} }
// Icon // Icon
img { .icon {
display: inline-block; display: inline-block;
width: 17px;
height: 17px;
margin-top: -5px; margin-top: -5px;
margin-right: 12px; margin-right: 12px;
} }

View File

@@ -0,0 +1,46 @@
/*
** Shop: About
*/
&__about {
background-color: $color-cream;
color: $color-text;
.description {
--columns: 8;
grid-column: 1 / span var(--columns);
margin-bottom: -96px;
padding: 88px 0 160px;
font-size: rem(18px);
font-weight: 200;
white-space: pre-line;
background: #fff;
border-radius: 0 0 12px 0;
box-shadow: 0 0 16px rgba(#000, 0.05);
@include bp (mob-lg) {
--columns: 7;
}
@include bp (sm) {
--columns: 17;
margin-bottom: -160px;
padding: 160px 0 274px;
font-size: rem(22px);
}
@include bp (lg) {
--columns: 13;
}
&__inner {
grid-column: 2 / span calc(var(--columns) - 2);
@include bp (sm) {
grid-column: 3 / span calc(var(--columns) - 5);
}
}
strong {
font-weight: 400;
color: $color-secondary;
}
}
}

View File

@@ -0,0 +1,201 @@
/*
** Shop: Intro
*/
&__intro {
position: relative;
z-index: 30;
height: 100vh;
min-height: 600px;
max-height: 1400px;
overflow: hidden;
// Top Menu
.top {
position: absolute;
z-index: 1;
top: 20px;
left: 0;
padding: 0 20px;
display: flex;
justify-content: space-between;
width: 100%;
margin: 0 auto;
@include bp (sm) {
top: 32px;
padding: 0 32px;
}
// Back
.back {
display: flex;
align-items: center;
color: #fff;
font-size: rem(14px);
color: $color-cream;
text-decoration: none;
transition: color 0.4s var(--ease-quart);
@include bp (sm) {
max-width: none;
font-size: rem(18px);
}
svg {
margin-right: 8px;
transition: transform 0.4s var(--ease-quart);
}
// Hover
&:hover {
color: $color-tertiary;
svg {
transform: translate3d(-4px, 0, 0);
}
}
}
// Shop
.shop-title {
font-size: rem(14px);
color: $color-cream;
@include bp (sm) {
font-size: rem(18px);
}
}
}
// Site Title
h1 {
position: absolute;
z-index: 2;
top: 50%;
left: 50%;
width: 100%;
transform: translate3d(-50%, -50%, 0);
// font-size: clamp(#{rem(88px)}, 10vw, #{rem(140px)});
text-shadow: 0px 8px 12px rgba(#000, 0.25);
text-align: center;
@include bp (sm) {
top: clamp(#{rem(40px)}, 16vw, #{rem(144px)});
left: 0;
font-size: clamp(#{rem(40px)}, 8vw, #{rem(96px)});
transform: none;
}
@include bp (md) {
top: clamp(#{rem(40px)}, 10vw, #{rem(96px)});
font-size: clamp(#{rem(40px)}, 6vw, #{rem(96px)});
}
span, strong {
color: #fff;
}
span {
font-weight: 300;
}
}
// Background Image
picture {
position: relative;
display: flex;
align-items: flex-end;
width: 100%;
height: 100%;
background-color: $color-primary-darker;
pointer-events: none;
user-select: none;
img {
opacity: 0.55;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
}
// Gradient
&:before {
display: block;
content: "";
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(180deg, rgba(45, 4, 88, 0) 84.2%, #1E0538 100%);
}
}
}
// Intro: Navigation
&__nav {
position: absolute;
z-index: 20;
bottom: 0;
left: 0;
width: 100%;
.container {
padding: 0 16px 24px;
@include bp (md) {
display: grid;
grid-template-columns: 15% auto 15%;
align-items: flex-end;
gap: 16px;
height: 100%;
padding: 0 32px 32px;
}
}
// Shop
p {
text-align: center;
margin-bottom: 8px;
@include bp (md) {
margin-right: auto;
}
}
// Navigation
nav {
ul {
display: flex;
align-items: center;
justify-content: center;
}
li {
display: block;
}
a {
text-decoration: none;
font-size: rem(22px);
font-family: $font-serif;
color: $color-tertiary;
margin: 0 10px;
transition: color 0.3s;
&:hover {
color: $color-secondary;
}
@include bp (sm) {
font-size: rem(24px);
margin: 0 12px;
}
}
// Active
.is-active {
a {
color: $color-secondary;
}
}
}
}

View File

@@ -0,0 +1,137 @@
/*
** Shop: Posters
*/
&__posters {
background-color: $color-primary-darker;
padding: 0 20px 72px;
border-bottom: solid 1px $color-primary-tertiary20 ;
@include bp (sm) {
padding: 120px 0;
}
// Title
h3 {
grid-column: 2 / span 6;
font-family: $font-serif;
color: $color-cream;
font-size: rem(32px);
line-height: 1.1;
text-align: center;
margin-bottom: 24px;
@include bp (sm) {
grid-column: 3 / span 14;
font-size: rem(48px);
text-align: left;
max-width: 360px;
margin-bottom: 0;
}
}
// Posters Set
.set {
--gap: 24px;
grid-column: 1 / span 8;
display: block;
@include bp (sm) {
grid-column: 2 / span 22;
}
@include bp (mob-lg) {
--gap: 32px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--gap);
}
@include bp (md) {
grid-template-columns: repeat(3, 1fr);
}
@include bp (sd) {
--gap: 48px;
grid-template-columns: repeat(4, 1fr);
}
// Poster
.poster {
margin: 0 auto auto;
// 2 columns
&:nth-child(2n + 1) {
@include bp (sm, max) {
margin-top: calc(var(--gap) * 2);
}
}
// 3 columns
&:nth-child(3n + 1) {
@include bp (sd, max) {
margin-top: calc(var(--gap) * 2);
}
}
&:nth-child(3n + 2) {
@include bp (sd, max) {
margin-top: var(--gap);
}
}
// 4 columns
&:nth-child(4n + 1) {
@include bp (sd) {
margin-top: 64px * 3;
}
}
&:nth-child(4n + 2) {
@include bp (sd) {
margin-top: 64px * 2;
}
}
&:nth-child(4n + 3) {
@include bp (sd) {
margin-top: 64px;
}
}
}
}
// Subscribe
.subscribe {
grid-column: 1 / span var(--columns);
max-width: 380px;
margin: 72px auto 0;
text-align: center;
@include bp (sm) {
grid-column: 14 / span 8;
margin-top: 0;
text-align: left;
}
p {
font-size: rem(18px);
color: $color-cream;
font-weight: 300;
line-height: 1.5;
margin-bottom: 24px;
@include bp (sm) {
font-size: rem(22px);
margin-bottom: 30px;
}
}
.newsletter-form {
padding: 0;
margin: 0 auto;
@include bp (sm) {
margin: 0;
}
}
.newsletter-form__bottom {
display: none;
}
}
}

View File

@@ -54,6 +54,8 @@
@import "molecules/newsletter-form"; @import "molecules/newsletter-form";
@import "molecules/poster"; @import "molecules/poster";
@import "molecules/notification-cart"; @import "molecules/notification-cart";
@import "molecules/cart-item";
@import "molecules/shop-locationswitcher";
// Organisms // Organisms
@import "organisms/locations"; @import "organisms/locations";

View File

@@ -93,7 +93,6 @@ export const clamp = (num: number, a: number, b: number) => {
} }
/** /**
* Return a random element from an array * Return a random element from an array
*/ */

View File

@@ -0,0 +1,40 @@
import { cartNotifications } from '$utils/stores/shop'
interface Notification {
title: string
name: string
image: string
timeout?: number
id?: number
}
/**
* Add a notification
*/
export const addNotification = (notification: Notification) => {
const id = Math.floor(Math.random() * 10000)
// Add ID and default timeout
notification = {
id,
timeout: notification.timeout || 3000,
...notification,
}
// Push the notifications to the top of the list of notificationss
cartNotifications.update((all) => [notification, ...all])
// If notification is dismissible, dismiss it after an amount of time (timeout)
if (notification.timeout) {
setTimeout(() => dismissNotification(id), notification.timeout)
}
}
/**
* Dismiss a notification
*/
export const dismissNotification = (id: number) => {
cartNotifications.update((all) => all.filter((t) => t.id !== id))
}

View File

@@ -0,0 +1,34 @@
import { addNotification } from '$utils/functions/notifications'
import { cartData } from '$utils/stores/shop'
/**
* Add a product to a cart
*/
export const addToCart = async (cartId: string, product: any, quantity: number = 1) => {
if (cartId && cartId !== 'null') {
const updatedCart = await fetch('/api/swell', {
method: 'POST',
body: JSON.stringify({
action: 'addToCart',
cartId,
productId: product.id,
quantity,
})
})
if (updatedCart.ok) {
const newCart = await updatedCart.json()
cartData.set(newCart)
// Show notification
addNotification({
title: 'Added to cart',
name: `${product.name} - x1`,
image: product.images[0].file.url,
})
}
} else {
console.log('No active cart')
}
}

View File

@@ -0,0 +1,156 @@
import swell from 'swell-node'
// Init Swell
swell.init(import.meta.env.VITE_SWELL_STORE_ID, import.meta.env.VITE_SWELL_API_TOKEN)
/**
* Fetch products
*/
export const getProducts = async (category?: string, limit: number = 25, page: number = 1) => {
const products = await swell.get('/products', {
where: {
active: true,
},
category,
limit,
page
})
if (products) {
return products
}
}
/**
* Retrieve a product
*/
export const getProduct = async (id: string) => {
const product = await swell.get('/products/{id}', {
id
})
if (product) {
return product
}
}
/**
* Create a cart
*/
export const createCart = async () => {
const cart = await swell.post('/carts')
if (cart) {
return cart
}
}
/**
* Retrieve cart
*/
export const fetchCart = async (id: string) => {
const cart = await swell.get('/carts/{id}', {
id,
expand: [
'items.product',
'items.variant',
]
})
if (cart) {
return cart
}
}
/**
* Add product to cart
*/
export const addToCart = async (cartId: string, productId: string, quantity: number) => {
// Fetch current cart data
const currentCart = await fetchCart(cartId)
// Updated cart with new items
const updatedCart = await swell.put('/carts/{id}', {
id: cartId,
items: [
...currentCart.items || [],
{
product_id: productId,
quantity,
}
],
})
if (updatedCart) {
// Fetch latest cart with updates
const cart = await fetchCart(cartId)
if (cart) {
return cart
}
}
}
/**
* Update cart item
*/
export const updateCartItem = async (cartId: string, productId: string, quantity: number) => {
// Fetch current cart data
const currentCart = await fetchCart(cartId)
// Updated items with replacing new item quantity
const updatedCartItems = currentCart.items.map((item: any) => {
// Replace item quantity with selected one
if (item.id === productId) {
item.quantity = quantity
}
return item
})
const updatedCart = await swell.put('/carts/{id}', {
id: cartId,
$set: {
items: updatedCartItems,
}
})
if (updatedCart) {
// Fetch latest cart with updates
const cart = await fetchCart(cartId)
if (cart) {
return cart
}
}
}
/**
* Remove cart item
*/
export const removeCartItem = async (cartId: string, productId: string) => {
// Fetch current cart data
const currentCart = await fetchCart(cartId)
// Updated items and remove selected item
const updatedCartItems = [...currentCart.items.filter((item: any) => {
return item.id !== productId
})]
const updatedCart = await swell.put('/carts/{id}', {
id: cartId,
$set: {
items: updatedCartItems,
}
})
if (updatedCart) {
// Fetch latest cart with updates
const cart = await fetchCart(cartId)
if (cart) {
return cart
}
}
}

View File

@@ -1,5 +0,0 @@
import { writable } from 'svelte/store'
// Shop
export const cartOpen = writable(false)
export const cartAmount = writable(3)

View File

43
src/utils/stores/shop.ts Normal file
View File

@@ -0,0 +1,43 @@
import { writable, derived } from 'svelte/store'
/**
* Shop
*/
export const shopLocations = writable([])
/**
* Cart
*/
/** Cart open state */
export const cartOpen = writable(false)
/** Cart open state */
export const cartId = writable(null)
// Write to localStorage when changing cartId
if (typeof localStorage !== 'undefined') {
if (localStorage.getItem('cartId')) {
// console.log('existant', localStorage.getItem('cartId'))
cartId.set(localStorage.getItem('cartId'))
}
cartId.subscribe(value => localStorage.setItem('cartId', value))
}
/** Raw Cart data */
export const cartData = writable(null)
/** Cart data is being updated */
export const cartIsUpdating = writable(false)
/** Amount of products present in cart */
export const cartAmount = derived(cartData, ($cart) => {
return $cart && $cart.item_quantity > 0 ? $cart.item_quantity : 0
})
/**
* Notifications
*/
export const cartNotifications = writable([])

View File

@@ -1,3 +0,0 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="#fff" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.57 13.193c.026-2.257.748-4.033 1.908-5.254A5.869 5.869 0 0 1 22.802 6.1c3.173.026 6.16 2.558 6.216 7.092h6.374c.925 0 1.655.783 1.586 1.702l-.037.485 4.13 27.93c.161 1.087-.903 1.95-1.938 1.571l-1.579-.576a.137.137 0 0 0-.121.014l-2.829 1.849a1.588 1.588 0 0 1-1.571 1.34H8.605a1.587 1.587 0 0 1-1.585-1.703l2.359-31.148a1.588 1.588 0 0 1 1.586-1.464h5.605Zm1.848 0c.026-1.858.614-3.16 1.403-3.99a4.017 4.017 0 0 1 2.965-1.262c2.073.017 4.332 1.644 4.384 5.252h-8.752Zm10.6 1.84h6.096l-.03.403h-.003l.002.01-2.29 30.221H8.884l2.32-30.634h5.366v5.233a2.111 2.111 0 0 0-1.197 1.901c0 1.166.95 2.112 2.12 2.112a2.111 2.111 0 0 0 .924-4.013v-5.233h8.755v5.233a2.111 2.111 0 0 0-1.197 1.901c0 1.166.95 2.112 2.12 2.112a2.111 2.111 0 0 0 .924-4.013v-5.233Zm5.767 28.815 1.523-20.115 2.839 19.193-.956-.349a1.991 1.991 0 0 0-1.772.203l-1.634 1.068Zm-16.789-21.68a.502.502 0 1 1-1.005-.003.502.502 0 0 1 1.005.002Zm10.099.5a.502.502 0 1 0 .002-1.004.502.502 0 0 0-.002 1.005Z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

View File

@@ -1,3 +0,0 @@
<svg width="16" height="19" viewBox="0 0 16 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.51787 13.244C8.29206 13.6955 7.64287 13.6955 7.41707 13.244C7.41707 13.244 5.27191 9.01059 4.25578 7.03501C3.23965 5.05943 4.02998 2.49118 6.09046 1.44695C8.15094 0.402716 10.663 1.22117 11.6792 3.28141C12.3001 4.52321 12.2719 5.84967 11.6792 7.03501C11.0864 8.22036 8.51787 13.244 8.51787 13.244ZM9.95738 5.22877C9.95738 4.12809 9.08239 3.25319 7.98158 3.25319C6.88078 3.25319 6.00578 4.15631 6.00578 5.22877C6.00578 6.32945 6.88078 7.20435 7.98158 7.20435C9.08239 7.20435 9.95738 6.32945 9.95738 5.22877ZM6.50898 15.1335C6.51939 15.1526 6.53135 15.171 6.54476 15.1888L6.44888 16.5251L10.0169 17.6779V13.4566C10.3586 12.8149 10.7654 12.0515 11.1765 11.2816V17.6779L14.7445 16.5251L14.2539 9.68778L11.4829 10.7079C11.7389 10.2289 11.9905 9.75854 12.223 9.32507L14.5215 8.47535C14.8705 8.37166 15.1411 8.6724 15.178 9.03754L15.9127 16.3004C15.9636 16.8029 15.6579 17.2724 15.1805 17.425L10.5967 18.8903L5.84678 17.4195L0.740081 18.8903C0.561681 18.9301 0.360981 18.9102 0.204881 18.7909C0.0487812 18.6717 -0.0181187 18.5127 0.00418125 18.3338L1.14148 10.4232C1.18608 10.2244 1.31988 10.0654 1.52058 10.0058L3.65978 9.3043C3.81969 9.61333 3.9812 9.92989 4.14174 10.248L2.23418 10.8605L1.25298 17.6779L5.33388 16.5052L5.57503 13.1639C6.1283 14.315 6.50898 15.1335 6.50898 15.1335Z" fill="#FFE0C5"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -1,3 +0,0 @@
<svg width="48" height="48" viewBox="0 0 48 48" fill="#fff" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.018 11.594a3.209 3.209 0 0 1 2.856-3.527l29.012-3.05a3.209 3.209 0 0 1 3.523 2.825l5.566.32a3.209 3.209 0 0 1 3.016 3.437l-1.633 22.378a3.209 3.209 0 0 1-3.322 2.973l-.57-.021a3.209 3.209 0 0 1-2.86 3.484l-29.012 3.05a3.209 3.209 0 0 1-3.527-2.857l-3.05-29.012Zm38.255 23.493L37.603 9.69l5.267.302a1.375 1.375 0 0 1 1.293 1.473l-1.634 22.379a1.375 1.375 0 0 1-1.424 1.274l-.832-.031ZM5.065 9.89a1.375 1.375 0 0 0-1.224 1.511L5.9 30.982l4.415-.464-.596-5.668a2.06 2.06 0 0 1 .584-1.665l6.249-6.322c.36-.365.83-.57 1.312-.607a.915.915 0 0 1 .302-.085l5.84-.614c.302-.032.605.043.858.211l7.154 4.78c.793.53.725 1.61.074 2.112l.597 5.506 4.912-.516a.959.959 0 0 1 .047-.004L35.59 8.066a1.375 1.375 0 0 0-1.511-1.225L5.065 9.891Zm26.23 22.435-1.007-9.288-2.845.3.953 9.064c.008.076.011.152.011.227l2.887-.303Zm-8.187 2.704 2.82-.297h.01l6.082-.64a1.298 1.298 0 0 0 1.155-1.43l-.29-2.674 4.907-.515a.957.957 0 0 0 .047-.006l.8 7.61a1.375 1.375 0 0 1-1.224 1.511L8.402 41.64a1.375 1.375 0 0 1-1.511-1.224l-.8-7.61 4.414-.463.202 1.919a2.06 2.06 0 0 0 2.265 1.834l3.307-.347a2.061 2.061 0 0 0 1.834-2.266l-.42-4.003c-.073-.685.446-1.353 1.229-1.435.782-.082 1.429.462 1.5 1.148l.421 4.004a2.06 2.06 0 0 0 2.265 1.834ZM22.246 29l.42 4.004a.227.227 0 0 0 .25.202l3.454-.363a.228.228 0 0 0 .202-.25l-.99-9.415a.227.227 0 0 0-.1-.165l-7.34-4.89a.227.227 0 0 0-.287.029l-6.249 6.322a.227.227 0 0 0-.064.184l.989 9.411a.227.227 0 0 0 .25.203l3.306-.348a.227.227 0 0 0 .202-.25l-.42-4.003c-.184-1.749 1.128-3.269 2.861-3.45 1.733-.183 3.332 1.03 3.516 2.78Zm4.382-7.42 3.139-.33-5.762-3.85-3.143.331 5.637 3.756c.044.03.087.06.129.093Z" />
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 KiB