diff --git a/.env.example b/.env.example index 892b1b6f..577a51ef 100644 --- a/.env.example +++ b/.env.example @@ -7,11 +7,11 @@ SPREE_PUBLISHABLE_KEY=your_publishable_api_key # Set these to your store's default_country_iso and default_locale NEXT_PUBLIC_DEFAULT_COUNTRY=us NEXT_PUBLIC_DEFAULT_LOCALE=en +NEXT_PUBLIC_SITE_URL=https://your-store.com NEXT_PUBLIC_STORE_NAME=Spree Store NEXT_PUBLIC_STORE_DESCRIPTION=A modern e-commerce storefront powered by Spree Commerce and Next.js. # Store SEO settings -NEXT_PUBLIC_SITE_URL=https://your-store-domain.com STORE_SEO_TITLE= STORE_META_DESCRIPTION= STORE_META_KEYWORDS= @@ -56,6 +56,3 @@ SENTRY_AUTH_TOKEN=your-auth-token # Set to "true" only if you have user consent or a privacy policy covering this. SENTRY_SEND_DEFAULT_PII=false NEXT_PUBLIC_SENTRY_SEND_DEFAULT_PII=false - -# Public site URL (required for sitemap and robots.txt generation) -NEXT_PUBLIC_SITE_URL=https://your-store.com diff --git a/emails/order-canceled.tsx b/emails/order-canceled.tsx index 568c5f12..559a80b4 100644 --- a/emails/order-canceled.tsx +++ b/emails/order-canceled.tsx @@ -5,8 +5,6 @@ export default function Preview() { + ); } diff --git a/emails/shipment-shipped.tsx b/emails/shipment-shipped.tsx index 0f28aea2..27194f97 100644 --- a/emails/shipment-shipped.tsx +++ b/emails/shipment-shipped.tsx @@ -5,8 +5,6 @@ export default function Preview() { =20.19.0" }, @@ -910,6 +912,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=20.19.0" } @@ -2327,7 +2330,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -2575,6 +2577,7 @@ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", + "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -2671,6 +2674,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2692,6 +2696,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.1.tgz", "integrity": "sha512-MHbu8XxCHcBn6RwvCt2Vpn1WnLMNECfNKYB14LI5XypcgH4IE0/DiVifVR9tAkwPMyLXN8dOoPJfya3IryLQVw==", "license": "Apache-2.0", + "peer": true, "engines": { "node": "^18.19.0 || >=20.6.0" }, @@ -2704,6 +2709,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.1.tgz", "integrity": "sha512-Dwlc+3HAZqpgTYq0MUyZABjFkcrKTePwuiFVLjahGD8cx3enqihmpAmdgNFO1R4m/sIe5afjJrA25Prqy4NXlA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -3127,6 +3133,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.1.tgz", "integrity": "sha512-BViBCdE/GuXRlp9k7nS1w6wJvY5fnFX5XvuEtWsTAOQFIO89Eru7lGW3WbfbxtCuZ/GbrJfAziXG0w0dpxL7eQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -3143,6 +3150,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.1.tgz", "integrity": "sha512-iZH3Gw8cxQn0gjpOjJMmKLd9GIaNh/E3v3ST67vyzLSxHBs14HsG4dy7jMYyC5WXGdBVEcM7U/XTF5hCQxjDMw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.5.1", "@opentelemetry/resources": "2.5.1", @@ -3160,6 +3168,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.40.0.tgz", "integrity": "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=14" } @@ -5018,6 +5027,7 @@ "resolved": "https://registry.npmjs.org/@react-email/body/-/body-0.3.0.tgz", "integrity": "sha512-uGo0BOOzjbMUo3lu+BIDWayvn5o6Xyfmnlla5VGf05n8gHMvO1ll7U4FtzWe3hxMLwt53pmc4iE0M+B5slG+Ug==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5030,6 +5040,7 @@ "resolved": "https://registry.npmjs.org/@react-email/button/-/button-0.2.1.tgz", "integrity": "sha512-qXyj7RZLE7POy9BMKSoqQ00tOXThjOZSUnI2Yu9i29IHngPlmrNayIWBoVKtElES7OWwypUcpiajwi1mUWx6/A==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5042,6 +5053,7 @@ "resolved": "https://registry.npmjs.org/@react-email/code-block/-/code-block-0.2.1.tgz", "integrity": "sha512-M3B7JpVH4ytgn83/ujRR1k1DQHvTeABiDM61OvAbjLRPhC/5KLHU5KkzIbbuGIrjWwxAbL1kSQzU8MhLEtSxyw==", "license": "MIT", + "peer": true, "dependencies": { "prismjs": "^1.30.0" }, @@ -5057,6 +5069,7 @@ "resolved": "https://registry.npmjs.org/@react-email/code-inline/-/code-inline-0.0.6.tgz", "integrity": "sha512-jfhebvv3dVsp3OdPgKXnk8+e2pBiDVZejDOBFzBa/IblrAJ9cQDkN6rBD5IyEg8hTOxwbw3iaI/yZFmDmIguIA==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5115,6 +5128,7 @@ "resolved": "https://registry.npmjs.org/@react-email/container/-/container-0.0.16.tgz", "integrity": "sha512-QWBB56RkkU0AJ9h+qy33gfT5iuZknPC7Un/IjZv9B0QmMIK+WWacc0cH6y2SV5Cv/b99hU94fjEMOOO4enpkbQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5151,6 +5165,7 @@ "resolved": "https://registry.npmjs.org/@react-email/heading/-/heading-0.0.16.tgz", "integrity": "sha512-jmsKnQm1ykpBzw4hCYHwBkt5pW2jScXffPeEH5ZRF5tZeF5b1pvlFTO9han7C0pCkZYo1kEvWiRtx69yfCIwuw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5163,6 +5178,7 @@ "resolved": "https://registry.npmjs.org/@react-email/hr/-/hr-0.0.12.tgz", "integrity": "sha512-TwmOmBDibavUQpXBxpmZYi2Iks/yeZOzFYh+di9EltMSnEabH8dMZXrl+pxNXzCgZ2XE8HY7VmUL65Lenfu5PA==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5187,6 +5203,7 @@ "resolved": "https://registry.npmjs.org/@react-email/img/-/img-0.0.12.tgz", "integrity": "sha512-sRCpEARNVTf3FQhZOC+JTvu5r6ubiYWkT0ucYXg8ctkyi4G8QG+jgYPiNUqVeTLA2STOfmPM/nrk1nb84y6CPQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5199,6 +5216,7 @@ "resolved": "https://registry.npmjs.org/@react-email/link/-/link-0.0.13.tgz", "integrity": "sha512-lkWc/NjOcefRZMkQoSDDbuKBEBDES9aXnFEOuPH845wD3TxPwh+QTf0fStuzjoRLUZWpHnio4z7qGGRYusn/sw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5226,6 +5244,7 @@ "resolved": "https://registry.npmjs.org/@react-email/preview/-/preview-0.0.14.tgz", "integrity": "sha512-aYK8q0IPkBXyMsbpMXgxazwHxYJxTrXrV95GFuu2HbEiIToMwSyUgb8HDFYwPqqfV03/jbwqlsXmFxsOd+VNaw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -5590,6 +5609,7 @@ "resolved": "https://registry.npmjs.org/@react-email/text/-/text-0.1.6.tgz", "integrity": "sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0.0" }, @@ -6525,6 +6545,7 @@ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.8.0.tgz", "integrity": "sha512-NNYuyW8qmLjyHnpyFgs/23wUrjB8k0xN9YIZFOMLewCa/pIkIji9e9aY/EgdNryEDDRptc6TcPIHRvG1R0ClFw==", "license": "MIT", + "peer": true, "engines": { "node": ">=12.16" } @@ -6732,6 +6753,7 @@ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -7186,8 +7208,7 @@ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/babel__core": { "version": "7.20.5", @@ -7276,7 +7297,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -7287,7 +7307,6 @@ "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -7303,8 +7322,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/mysql": { "version": "2.15.27", @@ -7350,6 +7368,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -7360,6 +7379,7 @@ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -7584,7 +7604,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" @@ -7594,29 +7613,25 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", @@ -7627,15 +7642,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7648,7 +7661,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "license": "MIT", - "peer": true, "dependencies": { "@xtuc/ieee754": "^1.2.0" } @@ -7658,7 +7670,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@xtuc/long": "4.2.2" } @@ -7667,15 +7678,13 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7692,7 +7701,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", @@ -7706,7 +7714,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", @@ -7719,7 +7726,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", @@ -7734,7 +7740,6 @@ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "license": "MIT", - "peer": true, "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" @@ -7744,15 +7749,13 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/accepts": { "version": "2.0.0", @@ -7797,6 +7800,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7818,7 +7822,6 @@ "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -7843,6 +7846,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7859,7 +7863,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "license": "MIT", - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -7877,7 +7880,6 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3" }, @@ -7900,7 +7902,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -8086,6 +8087,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8104,8 +8106,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/bundle-name": { "version": "4.1.0", @@ -8232,7 +8233,6 @@ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.0" } @@ -8849,8 +8849,7 @@ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/dom-serializer": { "version": "2.0.0", @@ -9207,7 +9206,6 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -9234,7 +9232,6 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -9247,7 +9244,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -9257,7 +9253,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } @@ -9282,7 +9277,6 @@ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.8.x" } @@ -9829,8 +9823,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/globals": { "version": "11.12.0", @@ -9874,7 +9867,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -9914,6 +9906,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -10361,7 +10354,6 @@ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -10980,7 +10972,6 @@ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.11.5" }, @@ -11068,7 +11059,6 @@ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "lz-string": "bin/bin.js" } @@ -11380,14 +11370,14 @@ "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/next": { "version": "16.2.1", "resolved": "https://registry.npmjs.org/next/-/next-16.2.1.tgz", "integrity": "sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q==", "license": "MIT", + "peer": true, "dependencies": { "@next/env": "16.2.1", "@swc/helpers": "0.5.15", @@ -12210,7 +12200,6 @@ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", @@ -12462,6 +12451,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12604,8 +12594,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/react-refresh": { "version": "0.18.0", @@ -12848,6 +12837,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -12978,7 +12968,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -13488,7 +13477,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -13697,7 +13685,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -13804,7 +13791,6 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -13823,7 +13809,6 @@ "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz", "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", @@ -13856,8 +13841,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/third-party-capital": { "version": "1.0.20", @@ -14014,6 +13998,7 @@ "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" @@ -14091,6 +14076,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14313,6 +14299,7 @@ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -14478,7 +14465,6 @@ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "license": "MIT", - "peer": true, "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -14511,7 +14497,6 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -14560,7 +14545,6 @@ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" } @@ -14569,8 +14553,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/whatwg-mimetype": { "version": "5.0.0", @@ -14882,6 +14865,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/app/[country]/[locale]/(checkout)/layout.tsx b/src/app/[country]/[locale]/(checkout)/layout.tsx index 860dd624..0fc1c05c 100644 --- a/src/app/[country]/[locale]/(checkout)/layout.tsx +++ b/src/app/[country]/[locale]/(checkout)/layout.tsx @@ -8,7 +8,7 @@ import { useTranslations } from "next-intl"; import { useState } from "react"; import { CheckoutProvider, CheckoutSummary } from "@/contexts/CheckoutContext"; import { POLICY_LINKS } from "@/lib/constants/policies"; -import { getStoreName } from "@/lib/seo"; +import { getStoreName } from "@/lib/store"; import { extractBasePath } from "@/lib/utils/path"; const storeName = getStoreName(); diff --git a/src/app/[country]/[locale]/(storefront)/c/[...permalink]/page.tsx b/src/app/[country]/[locale]/(storefront)/c/[...permalink]/page.tsx index c7528990..4c1e7b17 100644 --- a/src/app/[country]/[locale]/(storefront)/c/[...permalink]/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/c/[...permalink]/page.tsx @@ -5,7 +5,8 @@ import { Breadcrumbs } from "@/components/navigation/Breadcrumbs"; import { JsonLd } from "@/components/seo/JsonLd"; import { getCategory } from "@/lib/data/categories"; import { generateCategoryMetadata } from "@/lib/metadata/category"; -import { buildBreadcrumbJsonLd, getStoreUrl } from "@/lib/seo"; +import { buildBreadcrumbJsonLd } from "@/lib/seo"; +import { getStoreUrl } from "@/lib/store"; import { CategoryProductsContent } from "./CategoryProductsContent"; interface CategoryPageProps { diff --git a/src/app/[country]/[locale]/(storefront)/page.tsx b/src/app/[country]/[locale]/(storefront)/page.tsx index 893a79f8..bf0de745 100644 --- a/src/app/[country]/[locale]/(storefront)/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/page.tsx @@ -4,7 +4,7 @@ import { getTranslations } from "next-intl/server"; import { ProductCarousel } from "@/components/products/ProductCarousel"; import { Button } from "@/components/ui/button"; import { generateHomeMetadata } from "@/lib/metadata/home"; -import { getStoreName } from "@/lib/seo"; +import { getStoreName } from "@/lib/store"; interface HomePageProps { params: Promise<{ diff --git a/src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx b/src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx index 659eb9fc..61353a48 100644 --- a/src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/policies/[slug]/page.tsx @@ -2,7 +2,7 @@ import type { Metadata } from "next"; import { notFound } from "next/navigation"; import { getTranslations } from "next-intl/server"; import { getPolicy } from "@/lib/data/policies"; -import { getStoreName } from "@/lib/seo"; +import { getStoreName } from "@/lib/store"; interface PolicyPageProps { params: Promise<{ diff --git a/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx b/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx index 79874581..e57c1f7d 100644 --- a/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx +++ b/src/app/[country]/[locale]/(storefront)/products/[slug]/page.tsx @@ -9,8 +9,8 @@ import { buildBreadcrumbJsonLd, buildCanonicalUrl, buildProductJsonLd, - getStoreUrl, } from "@/lib/seo"; +import { getStoreUrl } from "@/lib/store"; import { ProductDetails } from "./ProductDetails"; interface ProductPageProps { diff --git a/src/app/[country]/[locale]/layout.tsx b/src/app/[country]/[locale]/layout.tsx index 53b37c14..eeb4db35 100644 --- a/src/app/[country]/[locale]/layout.tsx +++ b/src/app/[country]/[locale]/layout.tsx @@ -9,15 +9,13 @@ import { StoreProvider } from "@/contexts/StoreContext"; import { getMarkets } from "@/lib/data/markets"; import { generateStoreMetadata } from "@/lib/metadata/store"; import { buildOrganizationJsonLd } from "@/lib/seo"; +import { getDefaultCountry, getDefaultLocale } from "@/lib/store"; import deMessages from "../../../../messages/de.json"; import enMessages from "../../../../messages/en.json"; import esMessages from "../../../../messages/es.json"; import frMessages from "../../../../messages/fr.json"; import plMessages from "../../../../messages/pl.json"; -const DEFAULT_COUNTRY = process.env.NEXT_PUBLIC_DEFAULT_COUNTRY || "us"; -const DEFAULT_LOCALE = process.env.NEXT_PUBLIC_DEFAULT_LOCALE || "en"; - const messagesMap: Record = { en: enMessages, de: deMessages, @@ -62,8 +60,8 @@ export default async function CountryLocaleLayout({ if (!isValidCountry) { const defaultMarket = markets.find((m) => m.default) ?? markets[0]; const fallbackCountry = - defaultMarket?.countries?.[0]?.iso.toLowerCase() ?? DEFAULT_COUNTRY; - const fallbackLocale = defaultMarket?.default_locale ?? DEFAULT_LOCALE; + defaultMarket?.countries?.[0]?.iso.toLowerCase() ?? getDefaultCountry(); + const fallbackLocale = defaultMarket?.default_locale ?? getDefaultLocale(); redirect(`/${fallbackCountry}/${fallbackLocale}`); } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index b9767518..ad737589 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -5,6 +5,7 @@ import { Geist } from "next/font/google"; import "./globals.css"; import { Suspense } from "react"; import { CartProvider } from "@/contexts/CartContext"; +import { getStoreDescription, getStoreName } from "@/lib/store"; const gtmId = process.env.GTM_ID; @@ -13,16 +14,14 @@ const geist = Geist({ subsets: ["latin"], }); -const rootStoreName = process.env.NEXT_PUBLIC_STORE_NAME || "Spree Store"; +const rootStoreName = getStoreName(); export const metadata: Metadata = { title: { template: `%s | ${rootStoreName}`, default: rootStoreName, }, - description: - process.env.NEXT_PUBLIC_STORE_DESCRIPTION || - "A modern e-commerce storefront powered by Spree Commerce and Next.js.", + description: getStoreDescription(), }; export default function RootLayout({ diff --git a/src/app/robots.ts b/src/app/robots.ts index 299f3e5b..70e4857e 100644 --- a/src/app/robots.ts +++ b/src/app/robots.ts @@ -1,9 +1,9 @@ import type { MetadataRoute } from "next"; -import { ensureProtocol, getStoreUrl } from "@/lib/seo"; +import { getStoreUrl } from "@/lib/store"; import { generateSitemaps } from "./sitemap"; export default async function robots(): Promise { - const baseUrl = ensureProtocol(getStoreUrl() || "").replace(/\/$/, ""); + const baseUrl = (getStoreUrl() || "").replace(/\/$/, "") || undefined; const sitemaps = await generateSitemaps(); return { @@ -23,7 +23,11 @@ export default async function robots(): Promise { ], }, ], - sitemap: sitemaps.map((s) => `${baseUrl}/sitemap/${s.id}.xml`), - host: baseUrl, + ...(baseUrl + ? { + sitemap: sitemaps.map((s) => `${baseUrl}/sitemap/${s.id}.xml`), + host: baseUrl, + } + : {}), }; } diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 5ea21275..5fad5159 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -1,6 +1,6 @@ import type { Category, Media, Product } from "@spree/sdk"; -import { ensureProtocol, getStoreUrl } from "@/lib/seo"; import { getClient } from "@/lib/spree"; +import { getDefaultCountry, getDefaultLocale, getStoreUrl } from "@/lib/store"; type ProductWithMedia = Product & { media?: Media[]; @@ -40,8 +40,8 @@ const MAX_FETCHABLE_ITEMS = ITEMS_PER_PAGE * MAX_PAGES; */ function getDefaultLocaleOptions(): LocaleOptions { return { - locale: process.env.NEXT_PUBLIC_DEFAULT_LOCALE || "en", - country: process.env.NEXT_PUBLIC_DEFAULT_COUNTRY || "us", + locale: getDefaultLocale(), + country: getDefaultCountry(), }; } @@ -145,7 +145,7 @@ export default async function sitemap(props: { }): Promise { const id = Number(await props.id); - const candidate = ensureProtocol(getStoreUrl() || "").replace(/\/$/, ""); + const candidate = (getStoreUrl() || "").replace(/\/$/, ""); let baseUrl: string; try { diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 98b55ead..ca8fe2ac 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -2,9 +2,10 @@ import type { Category } from "@spree/sdk"; import Link from "next/link"; import { getTranslations } from "next-intl/server"; import { POLICY_LINKS } from "@/lib/constants/policies"; +import { getStoreDescription, getStoreName } from "@/lib/store"; -const storeName = process.env.NEXT_PUBLIC_STORE_NAME || "Spree Store"; -const envStoreDescription = process.env.NEXT_PUBLIC_STORE_DESCRIPTION; +const storeName = getStoreName(); +const storeDescription = getStoreDescription(); interface FooterProps { rootCategories: Category[]; @@ -28,7 +29,7 @@ export async function Footer({
{storeName}

- {t("description") || envStoreDescription} + {t("description") || storeDescription}

diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index 6362f18b..5793f94a 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -8,8 +8,9 @@ import { CountrySwitcher } from "@/components/layout/CountrySwitcher"; import { MobileMenu } from "@/components/layout/MobileMenu"; import { SearchToggle } from "@/components/layout/SearchToggle"; import { Button } from "@/components/ui/button"; +import { getStoreName } from "@/lib/store"; -const storeName = process.env.NEXT_PUBLIC_STORE_NAME || "Spree Store"; +const storeName = getStoreName(); interface HeaderProps { rootCategories: Category[]; diff --git a/src/contexts/StoreContext.tsx b/src/contexts/StoreContext.tsx index 62597f01..2bb5230f 100644 --- a/src/contexts/StoreContext.tsx +++ b/src/contexts/StoreContext.tsx @@ -2,7 +2,7 @@ import type { Country, Market } from "@spree/sdk"; import { createContext, type ReactNode, useContext, useMemo } from "react"; -import { getStoreName } from "@/lib/seo"; +import { getStoreName } from "@/lib/store"; /** Country enriched with market info (currency, locale, etc.) */ export interface CountryWithMarket extends Country { diff --git a/src/lib/emails/order-canceled.tsx b/src/lib/emails/order-canceled.tsx index 8acaadae..218801e2 100644 --- a/src/lib/emails/order-canceled.tsx +++ b/src/lib/emails/order-canceled.tsx @@ -13,6 +13,7 @@ import { Section, Text, } from "@react-email/components"; +import { getStoreName, getStoreUrl } from "@/lib/store"; interface LineItem { name: string; @@ -26,8 +27,8 @@ interface LineItem { interface OrderCanceledEmailProps { orderNumber: string; customerName: string; - storeName: string; - storeUrl: string; + storeName?: string; + storeUrl?: string; items: LineItem[]; displayTotal: string; } @@ -35,8 +36,8 @@ interface OrderCanceledEmailProps { export function OrderCanceledEmail({ orderNumber, customerName, - storeName, - storeUrl, + storeName = getStoreName(), + storeUrl = getStoreUrl(), items, displayTotal, }: OrderCanceledEmailProps) { @@ -109,10 +110,15 @@ export function OrderCanceledEmail({ - {storeName} -{" "} - - {storeUrl.replace(/^https?:\/\//, "")} - + {storeName} + {storeUrl && ( + <> + {" - "} + + {storeUrl.replace(/^https?:\/\//, "")} + + + )} diff --git a/src/lib/emails/order-confirmation.tsx b/src/lib/emails/order-confirmation.tsx index c0f60209..53a07866 100644 --- a/src/lib/emails/order-confirmation.tsx +++ b/src/lib/emails/order-confirmation.tsx @@ -13,6 +13,7 @@ import { Section, Text, } from "@react-email/components"; +import { getStoreName, getStoreUrl } from "@/lib/store"; interface LineItem { name: string; @@ -40,8 +41,8 @@ interface Address { interface OrderConfirmationEmailProps { orderNumber: string; customerName: string; - storeName: string; - storeUrl: string; + storeName?: string; + storeUrl?: string; items: LineItem[]; displayItemTotal: string; displayDeliveryTotal: string; @@ -56,8 +57,8 @@ interface OrderConfirmationEmailProps { export function OrderConfirmationEmail({ orderNumber, customerName, - storeName, - storeUrl, + storeName = getStoreName(), + storeUrl = getStoreUrl(), items, displayItemTotal, displayDeliveryTotal, @@ -110,14 +111,20 @@ export function OrderConfirmationEmail({ )} - - {item.name} - + {storeUrl ? ( + + {item.name} + + ) : ( + {item.name} + )} {item.options_text && ( {item.options_text} )} @@ -196,10 +203,15 @@ export function OrderConfirmationEmail({
- {storeName} -{" "} - - {storeUrl.replace(/^https?:\/\//, "")} - + {storeName} + {storeUrl && ( + <> + {" - "} + + {storeUrl.replace(/^https?:\/\//, "")} + + + )} diff --git a/src/lib/emails/password-reset.tsx b/src/lib/emails/password-reset.tsx index 3c447e94..50e0fc2e 100644 --- a/src/lib/emails/password-reset.tsx +++ b/src/lib/emails/password-reset.tsx @@ -11,17 +11,18 @@ import { Section, Text, } from "@react-email/components"; +import { getStoreName, getStoreUrl } from "@/lib/store"; interface PasswordResetEmailProps { resetUrl: string; - storeName: string; - storeUrl: string; + storeName?: string; + storeUrl?: string; } export function PasswordResetEmail({ resetUrl, - storeName, - storeUrl, + storeName = getStoreName(), + storeUrl = getStoreUrl(), }: PasswordResetEmailProps) { return ( @@ -55,10 +56,15 @@ export function PasswordResetEmail({ - {storeName} -{" "} - - {storeUrl.replace(/^https?:\/\//, "")} - + {storeName} + {storeUrl && ( + <> + {" - "} + + {storeUrl.replace(/^https?:\/\//, "")} + + + )} diff --git a/src/lib/emails/send.ts b/src/lib/emails/send.ts index 614cc3fb..2e0a695e 100644 --- a/src/lib/emails/send.ts +++ b/src/lib/emails/send.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import path from "node:path"; import { render } from "@react-email/render"; import type { ReactElement } from "react"; +import { getStoreEmailFrom, isStoreEmailFromFallback } from "@/lib/store"; interface SendEmailOptions { to: string; @@ -59,7 +60,13 @@ async function sendEmailDev({ to, subject, react }: SendEmailOptions) { async function sendEmailResend({ to, subject, react, from }: SendEmailOptions) { const { Resend } = await import("resend"); const resend = new Resend(process.env.RESEND_API_KEY); - const fromAddress = from || process.env.EMAIL_FROM || "orders@example.com"; + const fromAddress = from || getStoreEmailFrom(); + + if (!from && isStoreEmailFromFallback()) { + console.warn( + "[email] EMAIL_FROM is not set — using fallback 'orders@example.com' which will likely be rejected by Resend", + ); + } const { error } = await resend.emails.send({ from: fromAddress, diff --git a/src/lib/emails/shipment-shipped.tsx b/src/lib/emails/shipment-shipped.tsx index 5370ea33..95752261 100644 --- a/src/lib/emails/shipment-shipped.tsx +++ b/src/lib/emails/shipment-shipped.tsx @@ -14,6 +14,7 @@ import { Section, Text, } from "@react-email/components"; +import { getStoreName, getStoreUrl } from "@/lib/store"; interface ShippedItem { name: string; @@ -35,16 +36,16 @@ interface Shipment { interface ShipmentShippedEmailProps { orderNumber: string; customerName: string; - storeName: string; - storeUrl: string; + storeName?: string; + storeUrl?: string; shipments: Shipment[]; } export function ShipmentShippedEmail({ orderNumber, customerName, - storeName, - storeUrl, + storeName = getStoreName(), + storeUrl = getStoreUrl(), shipments, }: ShipmentShippedEmailProps) { const firstName = customerName.split(" ")[0] || "there"; @@ -109,16 +110,20 @@ export function ShipmentShippedEmail({ )}
- - {item.name} - + {storeUrl ? ( + + {item.name} + + ) : ( + {item.name} + )} {item.options_text && ( {item.options_text} )} @@ -133,10 +138,15 @@ export function ShipmentShippedEmail({
- {storeName} -{" "} - - {storeUrl.replace(/^https?:\/\//, "")} - + {storeName} + {storeUrl && ( + <> + {" - "} + + {storeUrl.replace(/^https?:\/\//, "")} + + + )} diff --git a/src/lib/metadata/categories.ts b/src/lib/metadata/categories.ts index 2c6fbffc..382a2955 100644 --- a/src/lib/metadata/categories.ts +++ b/src/lib/metadata/categories.ts @@ -1,5 +1,6 @@ import type { Metadata } from "next"; -import { buildCanonicalUrl, getStoreUrl } from "@/lib/seo"; +import { buildCanonicalUrl } from "@/lib/seo"; +import { getStoreUrl } from "@/lib/store"; interface CategoriesMetadataParams { country: string; diff --git a/src/lib/metadata/category.ts b/src/lib/metadata/category.ts index b1356bdb..ce9a69d3 100644 --- a/src/lib/metadata/category.ts +++ b/src/lib/metadata/category.ts @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { getCachedCategory } from "@/lib/data/cached"; -import { buildCanonicalUrl, getStoreUrl } from "@/lib/seo"; +import { buildCanonicalUrl } from "@/lib/seo"; +import { getStoreUrl } from "@/lib/store"; export interface CategoryMetadataParams { country: string; diff --git a/src/lib/metadata/home.ts b/src/lib/metadata/home.ts index d95a2110..9a88ed3a 100644 --- a/src/lib/metadata/home.ts +++ b/src/lib/metadata/home.ts @@ -1,11 +1,10 @@ import type { Metadata } from "next"; +import { buildCanonicalUrl, SOCIAL_IMAGE_PATH } from "@/lib/seo"; import { - buildCanonicalUrl, - getStoreDescription, - getStoreName, + getStoreMetaDescription, + getStoreSeoTitle, getStoreUrl, - SOCIAL_IMAGE_PATH, -} from "@/lib/seo"; +} from "@/lib/store"; interface HomeMetadataParams { country: string; @@ -16,9 +15,8 @@ export async function generateHomeMetadata({ country, locale, }: HomeMetadataParams): Promise { - const storeName = process.env.STORE_SEO_TITLE || getStoreName(); - const description = - process.env.STORE_META_DESCRIPTION || getStoreDescription(); + const storeName = getStoreSeoTitle(); + const description = getStoreMetaDescription(); const storeUrl = getStoreUrl(); const canonicalUrl = storeUrl ? buildCanonicalUrl(storeUrl, `/${country}/${locale}`) diff --git a/src/lib/metadata/product.ts b/src/lib/metadata/product.ts index d7e4019b..9f4ba61e 100644 --- a/src/lib/metadata/product.ts +++ b/src/lib/metadata/product.ts @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { getCachedProduct, PRODUCT_PAGE_EXPAND } from "@/lib/data/cached"; -import { buildCanonicalUrl, getStoreUrl, stripHtml } from "@/lib/seo"; +import { buildCanonicalUrl, stripHtml } from "@/lib/seo"; +import { getStoreUrl } from "@/lib/store"; interface ProductMetadataParams { country: string; diff --git a/src/lib/metadata/products.ts b/src/lib/metadata/products.ts index da87ae21..66fbd8bc 100644 --- a/src/lib/metadata/products.ts +++ b/src/lib/metadata/products.ts @@ -1,5 +1,6 @@ import type { Metadata } from "next"; -import { buildCanonicalUrl, getStoreUrl } from "@/lib/seo"; +import { buildCanonicalUrl } from "@/lib/seo"; +import { getStoreUrl } from "@/lib/store"; interface ProductsMetadataParams { country: string; diff --git a/src/lib/metadata/store.ts b/src/lib/metadata/store.ts index a2973241..f3d84ec8 100644 --- a/src/lib/metadata/store.ts +++ b/src/lib/metadata/store.ts @@ -1,11 +1,11 @@ import type { Metadata } from "next"; +import { SOCIAL_IMAGE_PATH } from "@/lib/seo"; import { - ensureProtocol, - getStoreDescription, + getStoreMetaDescription, getStoreName, + getStoreSeoTitle, getStoreUrl, - SOCIAL_IMAGE_PATH, -} from "@/lib/seo"; +} from "@/lib/store"; function normalizeOpenGraphLocale(locale: string): string { const parts = locale.split(/[-_]/); @@ -20,17 +20,16 @@ interface StoreMetadataParams { export async function generateStoreMetadata({ locale, }: StoreMetadataParams): Promise { - const storeName = process.env.STORE_SEO_TITLE || getStoreName(); + const storeName = getStoreSeoTitle(); const storeUrl = getStoreUrl(); - const metaDescription = - process.env.STORE_META_DESCRIPTION || getStoreDescription(); + const metaDescription = getStoreMetaDescription(); const metaKeywords = process.env.STORE_META_KEYWORDS; const twitter = process.env.STORE_TWITTER; let metadataBaseSpread: Partial<{ metadataBase: URL }> = {}; if (storeUrl) { try { - metadataBaseSpread = { metadataBase: new URL(ensureProtocol(storeUrl)) }; + metadataBaseSpread = { metadataBase: new URL(storeUrl) }; } catch { metadataBaseSpread = {}; } diff --git a/src/lib/seo.ts b/src/lib/seo.ts index 6241826c..b593a6fb 100644 --- a/src/lib/seo.ts +++ b/src/lib/seo.ts @@ -1,4 +1,5 @@ import type { Category, Media, Product } from "@spree/sdk"; +import { ensureProtocol, getStoreName, getStoreUrl } from "@/lib/store"; /** * Default social image path (stored in public/). @@ -6,48 +7,6 @@ import type { Category, Media, Product } from "@spree/sdk"; */ export const SOCIAL_IMAGE_PATH = "/social-image.png"; -/** - * Get the store URL, preferring NEXT_PUBLIC_SITE_URL and falling back to - * NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL. Returns undefined if neither - * variable is set. - */ -export function getStoreUrl(): string | undefined { - return ( - process.env.NEXT_PUBLIC_SITE_URL || - process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL || - undefined - ); -} - -/** - * Get the store name from environment variables. - */ -export function getStoreName(): string { - return process.env.NEXT_PUBLIC_STORE_NAME || "Spree Store"; -} - -/** - * Get the store description from environment variables. - */ -export function getStoreDescription(): string { - return ( - process.env.NEXT_PUBLIC_STORE_DESCRIPTION || - "A modern e-commerce storefront powered by Spree Commerce and Next.js." - ); -} - -/** - * Ensure a URL has a protocol prefix. - * If the URL doesn't start with http:// or https://, prepend https://. - */ -export function ensureProtocol(url: string): string { - const trimmed = url.trim(); - if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) { - return trimmed; - } - return `https://${trimmed}`; -} - /** * Build a full canonical URL from a store URL and a relative path. */ @@ -182,7 +141,7 @@ export function buildOrganizationJsonLd(): Record { "@context": "https://schema.org", "@type": "Organization", name: storeName, - ...(storeUrl ? { url: ensureProtocol(storeUrl) } : {}), + ...(storeUrl ? { url: storeUrl } : {}), }; if (logoUrl) { diff --git a/src/lib/store.ts b/src/lib/store.ts new file mode 100644 index 00000000..f2f6f9e5 --- /dev/null +++ b/src/lib/store.ts @@ -0,0 +1,96 @@ +/** + * Centralized store configuration. + * + * All store-level environment variables are accessed here so the rest of the + * codebase has a single source of truth with consistent fallback values. + */ + +/** + * Ensure a URL has a protocol prefix. + * If the URL doesn't start with http:// or https://, prepend https://. + */ +export function ensureProtocol(url: string): string { + const trimmed = url.trim(); + if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) { + return trimmed; + } + return `https://${trimmed}`; +} + +/** + * Get the store URL, preferring NEXT_PUBLIC_SITE_URL and falling back to + * NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL. In development, falls back to + * http://localhost:3001. Returns undefined in production when unconfigured + * so misconfiguration surfaces immediately. + */ +export function getStoreUrl(): string | undefined { + const raw = + process.env.NEXT_PUBLIC_SITE_URL || + process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL; + if (raw) return ensureProtocol(raw); + return process.env.NODE_ENV === "development" + ? "http://localhost:3001" + : undefined; +} + +/** + * Get the store name from environment variables. + */ +export function getStoreName(): string { + return process.env.NEXT_PUBLIC_STORE_NAME || "Spree Store"; +} + +/** + * Get the store description from environment variables. + */ +export function getStoreDescription(): string { + return ( + process.env.NEXT_PUBLIC_STORE_DESCRIPTION || + "A modern e-commerce storefront powered by Spree Commerce and Next.js." + ); +} + +/** + * Get the default country ISO code (lowercase). + */ +export function getDefaultCountry(): string { + return (process.env.NEXT_PUBLIC_DEFAULT_COUNTRY || "us").toLowerCase(); +} + +/** + * Get the default locale code. + */ +export function getDefaultLocale(): string { + return process.env.NEXT_PUBLIC_DEFAULT_LOCALE || "en"; +} + +/** + * Get the SEO title, preferring STORE_SEO_TITLE and falling back to the + * store name (NEXT_PUBLIC_STORE_NAME). + */ +export function getStoreSeoTitle(): string { + return process.env.STORE_SEO_TITLE || getStoreName(); +} + +/** + * Get the meta description, preferring STORE_META_DESCRIPTION and falling + * back to the store description (NEXT_PUBLIC_STORE_DESCRIPTION). + */ +export function getStoreMetaDescription(): string { + return process.env.STORE_META_DESCRIPTION || getStoreDescription(); +} + +/** + * Get the "from" address for transactional emails. + */ +export function getStoreEmailFrom(): string { + return process.env.EMAIL_FROM || "orders@example.com"; +} + +/** + * Returns true when EMAIL_FROM is not set and getStoreEmailFrom() will + * return the fallback address. + */ +export function isStoreEmailFromFallback(): boolean { + return !process.env.EMAIL_FROM; +} diff --git a/src/lib/webhooks/handlers.ts b/src/lib/webhooks/handlers.ts index a91f39e2..b4b44e1a 100644 --- a/src/lib/webhooks/handlers.ts +++ b/src/lib/webhooks/handlers.ts @@ -6,12 +6,16 @@ import { OrderConfirmationEmail } from "@/lib/emails/order-confirmation"; import { PasswordResetEmail } from "@/lib/emails/password-reset"; import { sendEmail } from "@/lib/emails/send"; import { ShipmentShippedEmail } from "@/lib/emails/shipment-shipped"; +import { getStoreName, getStoreUrl } from "@/lib/store"; -const STORE_NAME = process.env.NEXT_PUBLIC_STORE_NAME || "Store"; -const SITE_URL = - process.env.NEXT_PUBLIC_SITE_URL || - process.env.NEXT_PUBLIC_VERCEL_PROJECT_PRODUCTION_URL || - (process.env.NODE_ENV === "development" ? "http://localhost:3001" : ""); +const STORE_NAME = getStoreName(); +const SITE_URL = getStoreUrl(); + +if (!SITE_URL && process.env.NODE_ENV === "production") { + console.warn( + "[webhooks] NEXT_PUBLIC_SITE_URL is not set — email links will be broken", + ); +} /** * Idempotency guard — prevents duplicate email sends when Spree retries @@ -63,8 +67,6 @@ export async function handleOrderCompleted(event: WebhookEvent) { react: createElement(OrderConfirmationEmail, { orderNumber: order.number, customerName, - storeName: STORE_NAME, - storeUrl: SITE_URL, items: (order.items || []).map((item) => ({ name: item.name, slug: item.slug, @@ -105,8 +107,6 @@ export async function handleOrderCanceled(event: WebhookEvent) { react: createElement(OrderCanceledEmail, { orderNumber: order.number, customerName, - storeName: STORE_NAME, - storeUrl: SITE_URL, items: (order.items || []).map((item) => ({ name: item.name, slug: item.slug, @@ -173,8 +173,6 @@ export async function handleOrderShipped(event: WebhookEvent) { react: createElement(ShipmentShippedEmail, { orderNumber: order.number, customerName, - storeName: STORE_NAME, - storeUrl: SITE_URL, shipments, }), }); @@ -219,8 +217,6 @@ export async function handlePasswordReset( subject: `${STORE_NAME} Password Reset`, react: createElement(PasswordResetEmail, { resetUrl, - storeName: STORE_NAME, - storeUrl: SITE_URL, }), }); diff --git a/src/proxy.ts b/src/proxy.ts index 2bcd2029..bc959a88 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1,11 +1,9 @@ import { createSpreeMiddleware } from "@/lib/spree/middleware"; - -const DEFAULT_COUNTRY = process.env.NEXT_PUBLIC_DEFAULT_COUNTRY || "us"; -const DEFAULT_LOCALE = process.env.NEXT_PUBLIC_DEFAULT_LOCALE || "en"; +import { getDefaultCountry, getDefaultLocale } from "@/lib/store"; export const proxy = createSpreeMiddleware({ - defaultCountry: DEFAULT_COUNTRY, - defaultLocale: DEFAULT_LOCALE, + defaultCountry: getDefaultCountry(), + defaultLocale: getDefaultLocale(), }); export const config = {