Merge branch 'master' into 60-if-you-sign-out-the-basket-doesnt-get-cleared

This commit is contained in:
Josh Creek
2024-11-05 22:41:56 +00:00
committed by GitHub
18 changed files with 804 additions and 270 deletions
+2
View File
@@ -4,3 +4,5 @@ SUPABASE_SERVICE_ROLE_KEY=""
STRIPE_SECRET_KEY="" STRIPE_SECRET_KEY=""
STRIPE_ENDPOINT_SECRET="" STRIPE_ENDPOINT_SECRET=""
VITE_PRODUCT_ID_EXAMPLEPRODUCT="" VITE_PRODUCT_ID_EXAMPLEPRODUCT=""
VITE_AXIOM_DATASET=""
VITE_AXIOM_TOKEN=""
+3 -1
View File
@@ -47,10 +47,12 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@axiomhq/winston": "^1.2.0",
"@supabase/ssr": "^0.4.0", "@supabase/ssr": "^0.4.0",
"@supabase/supabase-js": "^2.44.2", "@supabase/supabase-js": "^2.44.2",
"stripe": "^15.4.0", "stripe": "^15.4.0",
"ts-debounce": "^4.0.0" "ts-debounce": "^4.0.0",
"winston": "^3.15.0"
}, },
"engines": { "engines": {
"node": ">=18.13.0" "node": ">=18.13.0"
+207
View File
@@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@axiomhq/winston':
specifier: ^1.2.0
version: 1.2.0
'@supabase/ssr': '@supabase/ssr':
specifier: ^0.4.0 specifier: ^0.4.0
version: 0.4.1(@supabase/supabase-js@2.45.4) version: 0.4.1(@supabase/supabase-js@2.45.4)
@@ -20,6 +23,9 @@ importers:
ts-debounce: ts-debounce:
specifier: ^4.0.0 specifier: ^4.0.0
version: 4.0.0 version: 4.0.0
winston:
specifier: ^3.15.0
version: 3.15.0
devDependencies: devDependencies:
'@sveltejs/adapter-auto': '@sveltejs/adapter-auto':
specifier: ^3.0.0 specifier: ^3.0.0
@@ -134,6 +140,14 @@ packages:
peerDependencies: peerDependencies:
ajv: '>=8' ajv: '>=8'
'@axiomhq/js@1.2.0':
resolution: {integrity: sha512-ALvNo8YeDwE7EHWr9TGijdfo/gbmgTB5XHHBcIcu6Ra+A9+2ltBO5UvTzvSSGPaxn4neEyh+8ESzEBJVEjNAnA==}
engines: {node: '>=16'}
'@axiomhq/winston@1.2.0':
resolution: {integrity: sha512-PE3dy49MZj9lrgTLcADu9iw8xeLxIA6Vcdc35JwgDDKDpvxy68+rz7LKWvhtnNi5T5Xpc5QMVjO3OGGDD35HrQ==}
engines: {node: '>=16'}
'@babel/code-frame@7.24.7': '@babel/code-frame@7.24.7':
resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@@ -709,6 +723,13 @@ packages:
'@canvas/image-data@1.0.0': '@canvas/image-data@1.0.0':
resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==} resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==}
'@colors/colors@1.6.0':
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
engines: {node: '>=0.1.90'}
'@dabh/diagnostics@2.0.3':
resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==}
'@esbuild/aix-ppc64@0.21.5': '@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -1187,6 +1208,9 @@ packages:
'@types/resolve@1.20.2': '@types/resolve@1.20.2':
resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==}
'@types/triple-beam@1.3.5':
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
'@types/trusted-types@2.0.7': '@types/trusted-types@2.0.7':
resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==}
@@ -1270,6 +1294,10 @@ packages:
'@vite-pwa/assets-generator': '@vite-pwa/assets-generator':
optional: true optional: true
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
acorn-jsx@5.3.2: acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies: peerDependencies:
@@ -1436,6 +1464,9 @@ packages:
buffer@5.7.1: buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
builtin-modules@3.3.0: builtin-modules@3.3.0:
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -1505,6 +1536,9 @@ packages:
color-string@1.9.1: color-string@1.9.1:
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
color@3.2.1:
resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==}
color@4.2.3: color@4.2.3:
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
engines: {node: '>=12.5.0'} engines: {node: '>=12.5.0'}
@@ -1512,6 +1546,9 @@ packages:
colorette@2.0.20: colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
colorspace@1.1.4:
resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==}
commander@12.1.0: commander@12.1.0:
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
engines: {node: '>=18'} engines: {node: '>=18'}
@@ -1683,6 +1720,9 @@ packages:
emoji-regex@9.2.2: emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
enabled@2.0.0:
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
end-of-stream@1.4.4: end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
@@ -1801,6 +1841,14 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
expand-template@2.0.3: expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -1838,10 +1886,16 @@ packages:
picomatch: picomatch:
optional: true optional: true
fecha@4.2.3:
resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
fetch-blob@3.2.0: fetch-blob@3.2.0:
resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==}
engines: {node: ^12.20 || >= 14.13} engines: {node: ^12.20 || >= 14.13}
fetch-retry@6.0.0:
resolution: {integrity: sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag==}
file-entry-cache@6.0.1: file-entry-cache@6.0.1:
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
@@ -1864,6 +1918,9 @@ packages:
flatted@3.3.1: flatted@3.3.1:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
fn.name@1.1.0:
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
for-each@0.3.3: for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
@@ -2229,6 +2286,9 @@ packages:
kolorist@1.8.0: kolorist@1.8.0:
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
kuler@2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
leven@3.1.0: leven@3.1.0:
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
engines: {node: '>=6'} engines: {node: '>=6'}
@@ -2273,6 +2333,10 @@ packages:
lodash@4.17.21: lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
logform@2.6.1:
resolution: {integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==}
engines: {node: '>= 12.0.0'}
lru-cache@10.4.3: lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
@@ -2420,6 +2484,9 @@ packages:
once@1.4.0: once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
one-time@1.0.0:
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
optionator@0.9.4: optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -2606,6 +2673,10 @@ packages:
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==} resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
engines: {node: ^14.13.1 || >=16.0.0} engines: {node: ^14.13.1 || >=16.0.0}
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
pump@3.0.2: pump@3.0.2:
resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==} resolution: {integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==}
@@ -2648,6 +2719,10 @@ packages:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
readable-stream@4.5.2:
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
readdirp@3.6.0: readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'} engines: {node: '>=8.10.0'}
@@ -2735,6 +2810,10 @@ packages:
resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
safe-stable-stringify@2.5.0:
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
engines: {node: '>=10'}
sander@0.5.1: sander@0.5.1:
resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==} resolution: {integrity: sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==}
@@ -2827,6 +2906,9 @@ packages:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
deprecated: Please use @jridgewell/sourcemap-codec instead deprecated: Please use @jridgewell/sourcemap-codec instead
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
streamx@2.20.1: streamx@2.20.1:
resolution: {integrity: sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==} resolution: {integrity: sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==}
@@ -3010,6 +3092,9 @@ packages:
text-decoder@1.2.0: text-decoder@1.2.0:
resolution: {integrity: sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==} resolution: {integrity: sha512-n1yg1mOj9DNpk3NeZOx7T6jchTbyJS3i3cucbNN6FcdPriMZx7NsgrGpWWdWZZGxD7ES1XB+3uoqHMgOKaN+fg==}
text-hex@1.0.0:
resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==}
text-table@0.2.0: text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@@ -3048,6 +3133,10 @@ packages:
tr46@1.0.1: tr46@1.0.1:
resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==}
triple-beam@1.4.1:
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
engines: {node: '>= 14.0.0'}
ts-api-utils@1.3.0: ts-api-utils@1.3.0:
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
@@ -3148,6 +3237,10 @@ packages:
util-deprecate@1.0.2: util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
vite-plugin-pwa@0.20.5: vite-plugin-pwa@0.20.5:
resolution: {integrity: sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==} resolution: {integrity: sha512-aweuI/6G6n4C5Inn0vwHumElU/UEpNuO+9iZzwPZGTCH87TeZ6YFMrEY6ZUBQdIHHlhTsbMDryFARcSuOdsz9Q==}
engines: {node: '>=16.0.0'} engines: {node: '>=16.0.0'}
@@ -3233,6 +3326,14 @@ packages:
engines: {node: '>= 8'} engines: {node: '>= 8'}
hasBin: true hasBin: true
winston-transport@4.8.0:
resolution: {integrity: sha512-qxSTKswC6llEMZKgCQdaWgDuMJQnhuvF5f2Nk3SNXc4byfQ+voo2mX1Px9dkNOuR8p0KAjfPG29PuYUSIb+vSA==}
engines: {node: '>= 12.0.0'}
winston@3.15.0:
resolution: {integrity: sha512-RhruH2Cj0bV0WgNL+lOfoUBI4DVfdUNjVnJGVovWZmrcKtrFTTRzgXYK2O9cymSGjrERCtaAeHwMNnUWXlwZow==}
engines: {node: '>= 12.0.0'}
word-wrap@1.2.5: word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@@ -3351,6 +3452,16 @@ snapshots:
jsonpointer: 5.0.1 jsonpointer: 5.0.1
leven: 3.1.0 leven: 3.1.0
'@axiomhq/js@1.2.0':
dependencies:
fetch-retry: 6.0.0
uuid: 8.3.2
'@axiomhq/winston@1.2.0':
dependencies:
'@axiomhq/js': 1.2.0
winston-transport: 4.8.0
'@babel/code-frame@7.24.7': '@babel/code-frame@7.24.7':
dependencies: dependencies:
'@babel/highlight': 7.24.7 '@babel/highlight': 7.24.7
@@ -4114,6 +4225,14 @@ snapshots:
'@canvas/image-data@1.0.0': {} '@canvas/image-data@1.0.0': {}
'@colors/colors@1.6.0': {}
'@dabh/diagnostics@2.0.3':
dependencies:
colorspace: 1.1.4
enabled: 2.0.0
kuler: 2.0.0
'@esbuild/aix-ppc64@0.21.5': '@esbuild/aix-ppc64@0.21.5':
optional: true optional: true
@@ -4562,6 +4681,8 @@ snapshots:
'@types/resolve@1.20.2': {} '@types/resolve@1.20.2': {}
'@types/triple-beam@1.3.5': {}
'@types/trusted-types@2.0.7': {} '@types/trusted-types@2.0.7': {}
'@types/ws@8.5.12': '@types/ws@8.5.12':
@@ -4668,6 +4789,10 @@ snapshots:
optionalDependencies: optionalDependencies:
'@vite-pwa/assets-generator': 0.2.6 '@vite-pwa/assets-generator': 0.2.6
abort-controller@3.0.0:
dependencies:
event-target-shim: 5.0.1
acorn-jsx@5.3.2(acorn@8.12.1): acorn-jsx@5.3.2(acorn@8.12.1):
dependencies: dependencies:
acorn: 8.12.1 acorn: 8.12.1
@@ -4857,6 +4982,11 @@ snapshots:
base64-js: 1.5.1 base64-js: 1.5.1
ieee754: 1.2.1 ieee754: 1.2.1
buffer@6.0.3:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
builtin-modules@3.3.0: {} builtin-modules@3.3.0: {}
cac@6.7.14: {} cac@6.7.14: {}
@@ -4931,6 +5061,11 @@ snapshots:
color-name: 1.1.4 color-name: 1.1.4
simple-swizzle: 0.2.2 simple-swizzle: 0.2.2
color@3.2.1:
dependencies:
color-convert: 1.9.3
color-string: 1.9.1
color@4.2.3: color@4.2.3:
dependencies: dependencies:
color-convert: 2.0.1 color-convert: 2.0.1
@@ -4938,6 +5073,11 @@ snapshots:
colorette@2.0.20: {} colorette@2.0.20: {}
colorspace@1.1.4:
dependencies:
color: 3.2.1
text-hex: 1.0.0
commander@12.1.0: {} commander@12.1.0: {}
commander@2.20.3: {} commander@2.20.3: {}
@@ -5086,6 +5226,8 @@ snapshots:
emoji-regex@9.2.2: {} emoji-regex@9.2.2: {}
enabled@2.0.0: {}
end-of-stream@1.4.4: end-of-stream@1.4.4:
dependencies: dependencies:
once: 1.4.0 once: 1.4.0
@@ -5303,6 +5445,10 @@ snapshots:
esutils@2.0.3: {} esutils@2.0.3: {}
event-target-shim@5.0.1: {}
events@3.3.0: {}
expand-template@2.0.3: {} expand-template@2.0.3: {}
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
@@ -5333,11 +5479,15 @@ snapshots:
optionalDependencies: optionalDependencies:
picomatch: 4.0.2 picomatch: 4.0.2
fecha@4.2.3: {}
fetch-blob@3.2.0: fetch-blob@3.2.0:
dependencies: dependencies:
node-domexception: 1.0.0 node-domexception: 1.0.0
web-streams-polyfill: 3.3.3 web-streams-polyfill: 3.3.3
fetch-retry@6.0.0: {}
file-entry-cache@6.0.1: file-entry-cache@6.0.1:
dependencies: dependencies:
flat-cache: 3.2.0 flat-cache: 3.2.0
@@ -5363,6 +5513,8 @@ snapshots:
flatted@3.3.1: {} flatted@3.3.1: {}
fn.name@1.1.0: {}
for-each@0.3.3: for-each@0.3.3:
dependencies: dependencies:
is-callable: 1.2.7 is-callable: 1.2.7
@@ -5706,6 +5858,8 @@ snapshots:
kolorist@1.8.0: {} kolorist@1.8.0: {}
kuler@2.0.0: {}
leven@3.1.0: {} leven@3.1.0: {}
levn@0.4.1: levn@0.4.1:
@@ -5737,6 +5891,15 @@ snapshots:
lodash@4.17.21: {} lodash@4.17.21: {}
logform@2.6.1:
dependencies:
'@colors/colors': 1.6.0
'@types/triple-beam': 1.3.5
fecha: 4.2.3
ms: 2.1.3
safe-stable-stringify: 2.5.0
triple-beam: 1.4.1
lru-cache@10.4.3: {} lru-cache@10.4.3: {}
lru-cache@5.1.1: lru-cache@5.1.1:
@@ -5854,6 +6017,10 @@ snapshots:
dependencies: dependencies:
wrappy: 1.0.2 wrappy: 1.0.2
one-time@1.0.0:
dependencies:
fn.name: 1.1.0
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
deep-is: 0.1.4 deep-is: 0.1.4
@@ -6012,6 +6179,8 @@ snapshots:
pretty-bytes@6.1.1: {} pretty-bytes@6.1.1: {}
process@0.11.10: {}
pump@3.0.2: pump@3.0.2:
dependencies: dependencies:
end-of-stream: 1.4.4 end-of-stream: 1.4.4
@@ -6062,6 +6231,14 @@ snapshots:
string_decoder: 1.3.0 string_decoder: 1.3.0
util-deprecate: 1.0.2 util-deprecate: 1.0.2
readable-stream@4.5.2:
dependencies:
abort-controller: 3.0.0
buffer: 6.0.3
events: 3.3.0
process: 0.11.10
string_decoder: 1.3.0
readdirp@3.6.0: readdirp@3.6.0:
dependencies: dependencies:
picomatch: 2.3.1 picomatch: 2.3.1
@@ -6171,6 +6348,8 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
is-regex: 1.1.4 is-regex: 1.1.4
safe-stable-stringify@2.5.0: {}
sander@0.5.1: sander@0.5.1:
dependencies: dependencies:
es6-promise: 3.3.1 es6-promise: 3.3.1
@@ -6280,6 +6459,8 @@ snapshots:
sourcemap-codec@1.4.8: {} sourcemap-codec@1.4.8: {}
stack-trace@0.0.10: {}
streamx@2.20.1: streamx@2.20.1:
dependencies: dependencies:
fast-fifo: 1.3.2 fast-fifo: 1.3.2
@@ -6546,6 +6727,8 @@ snapshots:
dependencies: dependencies:
b4a: 1.6.6 b4a: 1.6.6
text-hex@1.0.0: {}
text-table@0.2.0: {} text-table@0.2.0: {}
thenify-all@1.6.0: thenify-all@1.6.0:
@@ -6582,6 +6765,8 @@ snapshots:
dependencies: dependencies:
punycode: 2.3.1 punycode: 2.3.1
triple-beam@1.4.1: {}
ts-api-utils@1.3.0(typescript@5.6.2): ts-api-utils@1.3.0(typescript@5.6.2):
dependencies: dependencies:
typescript: 5.6.2 typescript: 5.6.2
@@ -6684,6 +6869,8 @@ snapshots:
util-deprecate@1.0.2: {} util-deprecate@1.0.2: {}
uuid@8.3.2: {}
vite-plugin-pwa@0.20.5(@vite-pwa/assets-generator@0.2.6)(vite@5.4.6(@types/node@22.5.5)(terser@5.33.0))(workbox-build@7.1.1)(workbox-window@7.1.0): vite-plugin-pwa@0.20.5(@vite-pwa/assets-generator@0.2.6)(vite@5.4.6(@types/node@22.5.5)(terser@5.33.0))(workbox-build@7.1.1)(workbox-window@7.1.0):
dependencies: dependencies:
debug: 4.3.7 debug: 4.3.7
@@ -6758,6 +6945,26 @@ snapshots:
dependencies: dependencies:
isexe: 2.0.0 isexe: 2.0.0
winston-transport@4.8.0:
dependencies:
logform: 2.6.1
readable-stream: 4.5.2
triple-beam: 1.4.1
winston@3.15.0:
dependencies:
'@colors/colors': 1.6.0
'@dabh/diagnostics': 2.0.3
async: 3.2.6
is-stream: 2.0.1
logform: 2.6.1
one-time: 1.0.0
readable-stream: 3.6.2
safe-stable-stringify: 2.5.0
stack-trace: 0.0.10
triple-beam: 1.4.1
winston-transport: 4.8.0
word-wrap@1.2.5: {} word-wrap@1.2.5: {}
workbox-background-sync@7.1.0: workbox-background-sync@7.1.0:
+3 -1
View File
@@ -21,7 +21,9 @@
<div class="fixed bottom-0 left-0 w-full bg-gray-800 text-white p-4"> <div class="fixed bottom-0 left-0 w-full bg-gray-800 text-white p-4">
<p class="text-sm"> <p class="text-sm">
We use cookies to ensure you get the best experience on our website. By continuing, you agree We use cookies to ensure you get the best experience on our website. By continuing, you agree
to our use of cookies. to our use of cookies. For details, please see our <a href="/privacy" class="underline"
>Privacy Policy</a
>.
</p> </p>
<button class="bg-blue-500 hover:bg-blue-600 px-4 py-2 rounded" on:click={acceptCookies} <button class="bg-blue-500 hover:bg-blue-600 px-4 py-2 rounded" on:click={acceptCookies}
>Accept</button >Accept</button
-126
View File
@@ -1,126 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte';
import { general } from '$lib/stores/generalStore.js';
const dispatch = createEventDispatcher();
function emitSignedInEvent() {
dispatch('signedIn', {});
}
let email = '';
let password = '';
// Access the supabase client from the layout data
export let supabase: any;
async function signInWithEmail() {
// TODO use the data and error from the response
const { data, error } = await supabase.auth
.signInWithPassword({
email: email,
password: password
})
.then(() => {
emitSignedInEvent();
});
}
async function forgotPassword() {
if (email === '') {
general.update((value) => {
return {
...value,
hideToast: false,
toastMessage: 'Please enter your email address',
toastType: 'error'
};
});
setTimeout(() => {
general.update((value) => {
return { ...value, hideToast: true, toastMessage: '', toastType: 'success' };
});
}, 5000);
return;
}
const { data, error } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: 'https://example.com/auth/update-password'
});
if (error) {
console.error('Error sending password reset email:', error.message);
general.update((value) => {
return {
...value,
hideToast: false,
toastMessage: 'Error sending password reset email. Please try again.',
toastType: 'error'
};
});
} else {
console.log('Password reset email sent:', data);
general.update((value) => {
return {
...value,
hideToast: false,
toastMessage: 'Password reset email sent. Please check your inbox.',
toastType: 'success'
};
});
}
setTimeout(() => {
general.update((value) => {
return { ...value, hideToast: true, toastMessage: '', toastType: 'success' };
});
}, 5000);
}
</script>
<div class="card shrink-0 w-full max-w-sm shadow-2xl bg-base-100">
<form class="card-body">
<div class="form-control">
<label class="input input-bordered flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4 opacity-70"
><path
d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z"
/><path
d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z"
/>
</svg>
<input type="text" class="grow" placeholder="Email" required bind:value={email} />
</label>
</div>
<div class="form-control">
<label class="input input-bordered flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4 opacity-70"
><path
fill-rule="evenodd"
d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z"
clip-rule="evenodd"
/>
</svg>
<input type="password" class="grow" placeholder="Password" required bind:value={password} />
</label>
</div>
<div class="form-control">
<button id="forgot-password" class="label-text-alt link link-hover" on:click={forgotPassword}>
Forgot your password?
</button>
</div>
<div class="form-control mt-2">
<button class="btn btn-primary" on:click={signInWithEmail}>Sign In</button>
</div>
</form>
</div>
-68
View File
@@ -1,68 +0,0 @@
<script lang="ts">
let email = '';
let password = '';
// Access the supabase client from the layout data
export let supabase: any;
async function signUpNewUser() {
try {
const { data, error } = await supabase.auth.signUp({
email: email,
password: password,
options: {
// Redirect URL after successful sign-up
redirectTo: '/account'
}
});
if (error) {
throw error;
}
// Handle success (optional)
} catch (error: any) {
console.error('Sign up error:', error.message);
// Handle error
}
}
</script>
<form class="card-body">
<div class="form-control">
<label class="input input-bordered flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4 opacity-70"
><path
d="M2.5 3A1.5 1.5 0 0 0 1 4.5v.793c.026.009.051.02.076.032L7.674 8.51c.206.1.446.1.652 0l6.598-3.185A.755.755 0 0 1 15 5.293V4.5A1.5 1.5 0 0 0 13.5 3h-11Z"
/><path
d="M15 6.954 8.978 9.86a2.25 2.25 0 0 1-1.956 0L1 6.954V11.5A1.5 1.5 0 0 0 2.5 13h11a1.5 1.5 0 0 0 1.5-1.5V6.954Z"
/>
</svg>
<input type="text" class="grow" placeholder="Email" required bind:value={email} />
</label>
</div>
<div class="form-control">
<label class="input input-bordered flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 16 16"
fill="currentColor"
class="w-4 h-4 opacity-70"
><path
fill-rule="evenodd"
d="M14 6a4 4 0 0 1-4.899 3.899l-1.955 1.955a.5.5 0 0 1-.353.146H5v1.5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1-.5-.5v-2.293a.5.5 0 0 1 .146-.353l3.955-3.955A4 4 0 1 1 14 6Zm-4-2a.75.75 0 0 0 0 1.5.5.5 0 0 1 .5.5.75.75 0 0 0 1.5 0 2 2 0 0 0-2-2Z"
clip-rule="evenodd"
/>
</svg>
<input type="password" class="grow" placeholder="Password" required bind:value={password} />
</label>
</div>
<div class="form-control mt-6">
<button class="btn btn-primary" on:click={signUpNewUser}>Sign Up</button>
</div>
</form>
+40
View File
@@ -0,0 +1,40 @@
import { createLogger, transports, format } from 'winston';
import { WinstonTransport as AxiomTransport } from '@axiomhq/winston';
import { VITE_AXIOM_DATASET, VITE_AXIOM_TOKEN } from '$env/static/private';
const axiomTransport = new AxiomTransport({
dataset: VITE_AXIOM_DATASET,
token: VITE_AXIOM_TOKEN
});
const logger = createLogger({
level: 'info',
defaultMeta: { service: 'example-site' }, // Change this so you can identify your website if you have multiple logging into Axiom
format: format.combine(
format.errors({ stack: true }), // Captures error stack traces
format.timestamp(),
format.json() // Formats logs as JSON for structured logging
),
transports: [
new transports.Console({
format: format.combine(
format.colorize(),
format.printf(({ timestamp, level, message, stack }) => {
// Stack traces will be included in error messages automatically
return `${timestamp} [${level.toUpperCase()}]: ${message}${stack ? `\n${stack}` : ''}`;
})
)
}),
axiomTransport
],
exceptionHandlers: [axiomTransport], // Logs uncaught exceptions
rejectionHandlers: [axiomTransport] // Logs unhandled promise rejections
});
// Uncomment this for testing your initial logging setup
// logger.log({
// level: 'info',
// message: 'Logger successfully setup'
// });
export default logger;
+142 -36
View File
@@ -4,6 +4,7 @@ import type { Database, Tables, TablesInsert } from '$lib/types/supabase';
import { PUBLIC_SUPABASE_URL } from '$env/static/public'; import { PUBLIC_SUPABASE_URL } from '$env/static/public';
import { SUPABASE_SERVICE_ROLE_KEY } from '$env/static/private'; import { SUPABASE_SERVICE_ROLE_KEY } from '$env/static/private';
import { stripe as stripeClient } from '$lib/utils/stripe'; import { stripe as stripeClient } from '$lib/utils/stripe';
import logger from '$lib/utils/logger/logger';
const toDateTime = (secs: number) => { const toDateTime = (secs: number) => {
const t = new Date(+0); // Unix epoch start. const t = new Date(+0); // Unix epoch start.
@@ -39,7 +40,7 @@ const upsertProductRecord = async (product: stripe.Product) => {
const { error: upsertError } = await supabaseAdmin.from('products').upsert([productData]); const { error: upsertError } = await supabaseAdmin.from('products').upsert([productData]);
if (upsertError) throw new Error(`Product insert/update failed: ${upsertError.message}`); if (upsertError) throw new Error(`Product insert/update failed: ${upsertError.message}`);
console.log(`Product inserted/updated: ${product.id}`); logger.info(`Product inserted/updated: ${product.id}`);
}; };
const upsertPriceRecord = async (price: stripe.Price, retryCount = 0, maxRetries = 3) => { const upsertPriceRecord = async (price: stripe.Price, retryCount = 0, maxRetries = 3) => {
@@ -61,7 +62,7 @@ const upsertPriceRecord = async (price: stripe.Price, retryCount = 0, maxRetries
if (upsertError?.message.includes('foreign key constraint')) { if (upsertError?.message.includes('foreign key constraint')) {
if (retryCount < maxRetries) { if (retryCount < maxRetries) {
console.log(`Retry attempt ${retryCount + 1} for price ID: ${price.id}`); logger.info(`Retry attempt ${retryCount + 1} for price ID: ${price.id}`);
await new Promise((resolve) => setTimeout(resolve, 2000)); await new Promise((resolve) => setTimeout(resolve, 2000));
await upsertPriceRecord(price, retryCount + 1, maxRetries); await upsertPriceRecord(price, retryCount + 1, maxRetries);
} else { } else {
@@ -72,7 +73,7 @@ const upsertPriceRecord = async (price: stripe.Price, retryCount = 0, maxRetries
} else if (upsertError) { } else if (upsertError) {
throw new Error(`Price insert/update failed: ${upsertError.message}`); throw new Error(`Price insert/update failed: ${upsertError.message}`);
} else { } else {
console.log(`Price inserted/updated: ${price.id}`); logger.info(`Price inserted/updated: ${price.id}`);
} }
}; };
@@ -82,13 +83,13 @@ const deleteProductRecord = async (product: stripe.Product) => {
.delete() .delete()
.eq('id', product.id); .eq('id', product.id);
if (deletionError) throw new Error(`Product deletion failed: ${deletionError.message}`); if (deletionError) throw new Error(`Product deletion failed: ${deletionError.message}`);
console.log(`Product deleted: ${product.id}`); logger.info(`Product deleted: ${product.id}`);
}; };
const deletePriceRecord = async (price: stripe.Price) => { const deletePriceRecord = async (price: stripe.Price) => {
const { error: deletionError } = await supabaseAdmin.from('prices').delete().eq('id', price.id); const { error: deletionError } = await supabaseAdmin.from('prices').delete().eq('id', price.id);
if (deletionError) throw new Error(`Price deletion failed: ${deletionError.message}`); if (deletionError) throw new Error(`Price deletion failed: ${deletionError.message}`);
console.log(`Price deleted: ${price.id}`); logger.info(`Price deleted: ${price.id}`);
}; };
const upsertCustomerToSupabase = async (uuid: string, customerId: string) => { const upsertCustomerToSupabase = async (uuid: string, customerId: string) => {
@@ -254,11 +255,18 @@ const manageSubscriptionStatusChange = async (
trial_end: subscription.trial_end ? toDateTime(subscription.trial_end).toISOString() : null trial_end: subscription.trial_end ? toDateTime(subscription.trial_end).toISOString() : null
}; };
const { error: upsertError } = await supabaseAdmin try {
.from('subscriptions') const { error: upsertError } = await supabaseAdmin
.upsert([subscriptionData]); .from('subscriptions')
if (upsertError) throw new Error(`Subscription insert/update failed: ${upsertError.message}`); .upsert([subscriptionData]);
console.log(`Inserted/updated subscription [${subscription.id}] for user [${uuid}]`); if (upsertError) {
throw new Error(`Subscription insert/update failed: ${upsertError.message}`);
}
} catch (error) {
logger.error(error);
}
logger.info(`Inserted/updated subscription [${subscription.id}] for user [${uuid}]`);
// // For a new subscription copy the billing details to the customer object. // // For a new subscription copy the billing details to the customer object.
// // NOTE: This is a costly operation and should happen at the very end. // // NOTE: This is a costly operation and should happen at the very end.
@@ -271,7 +279,7 @@ const manageSubscriptionStatusChange = async (
}; };
const recordProductPurchase = async (userId: string, productId: string, priceId: string) => { const recordProductPurchase = async (userId: string, productId: string, priceId: string) => {
console.log( logger.info(
`Recording product purchase for user [${userId}] and product [${productId}] with price [${priceId}]` `Recording product purchase for user [${userId}] and product [${productId}] with price [${priceId}]`
); );
@@ -284,40 +292,48 @@ const recordProductPurchase = async (userId: string, productId: string, priceId:
throw new Error(`Failed to record product purchase: ${error.message}`); throw new Error(`Failed to record product purchase: ${error.message}`);
} }
} catch (error) { } catch (error) {
console.error(error); logger.error(error);
} }
console.log(`Product purchased recorded for user [${userId}] and product [${productId}]`); logger.info(`Product purchased recorded for user [${userId}] and product [${productId}]`);
}; };
const hasProductAccess = async (userId: string, productId: string) => { const hasProductAccess = async (userId: string, productId: string) => {
// Check if user has purchased the product // Check if user has purchased the product
const { data: purchases, error: purchaseError } = await supabaseAdmin try {
.from('purchases') const { data: purchases, error: purchaseError } = await supabaseAdmin
.select('*') .from('purchases')
.eq('user_id', userId) .select('*')
.eq('product_id', productId); .eq('user_id', userId)
.eq('product_id', productId);
if (purchaseError) throw new Error(purchaseError.message); if (purchaseError) throw new Error(purchaseError.message);
if (purchases && purchases.length > 0) { if (purchases && purchases.length > 0) {
// User has purchased the product // User has purchased the product
return true; return true;
}
} catch (error) {
logger.error(error);
} }
// Check if user has an active subscription for the product // Check if user has an active subscription for the product
const { data: subscriptions, error: subError } = await supabaseAdmin try {
.from('subscriptions') const { data: subscriptions, error: subError } = await supabaseAdmin
.select('*') .from('subscriptions')
.eq('user_id', userId) .select('*')
.eq('product_id', productId) .eq('user_id', userId)
.in('status', ['active', 'trialing']); .eq('product_id', productId)
.in('status', ['active', 'trialing']);
if (subError) throw new Error(subError.message); if (subError) throw new Error(subError.message);
if (subscriptions && subscriptions.length > 0) { if (subscriptions && subscriptions.length > 0) {
// User has an active subscription // User has an active subscription
return true; return true;
}
} catch (error) {
logger.error(error);
} }
return false; return false;
@@ -381,7 +397,7 @@ const updateUserCredits = async (userId: string, creditsChange: number, descript
await logCreditTransaction(userId, creditsChange, description); await logCreditTransaction(userId, creditsChange, description);
console.log( logger.info(
`${creditsChange > 0 ? 'Added' : 'Deducted'} ${Math.abs(creditsChange)} credits to user [${userId}]` `${creditsChange > 0 ? 'Added' : 'Deducted'} ${Math.abs(creditsChange)} credits to user [${userId}]`
); );
}; };
@@ -390,7 +406,7 @@ const addCredits = async (userId: string, creditsToAdd: number, description: str
try { try {
await updateUserCredits(userId, creditsToAdd, description); await updateUserCredits(userId, creditsToAdd, description);
} catch (error) { } catch (error) {
console.error(error); logger.error(error);
} }
}; };
@@ -398,7 +414,7 @@ const deductCredits = async (userId: string, creditsToDeduct: number, descriptio
try { try {
await updateUserCredits(userId, -creditsToDeduct, description); await updateUserCredits(userId, -creditsToDeduct, description);
} catch (error) { } catch (error) {
console.error(error); logger.error(error);
} }
}; };
@@ -480,6 +496,94 @@ const getProductById = async (productId: string) => {
return product as Product; return product as Product;
}; };
const getUserSubscriptions = async (userId: string) => {
try {
// Step 1: Retrieve subscription data
const { data: subscriptions, error } = await supabaseAdmin
.from('subscriptions')
.select('*')
.eq('user_id', userId);
if (error) {
logger.error('Error retrieving subscriptions:', error.message);
throw new Error('Failed to fetch subscriptions');
}
// Step 2: Map through each subscription and fetch product and price details
const subscriptionDetails = await Promise.all(
subscriptions.map(async (sub) => {
// Fetch product and price details from Stripe
const product = await stripeClient.products.retrieve(sub.product_id);
const price = await stripeClient.prices.retrieve(sub.price_id);
const expiryDate = new Date(sub.current_period_end);
// Format output data
return {
productName: product.name,
amount: (price.unit_amount / 100).toFixed(2),
currency: price.currency.toUpperCase(),
interval: price.recurring?.interval || 'one-time',
expiryDate: expiryDate.toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
}),
status: sub.status
};
})
);
return subscriptionDetails;
} catch (error) {
logger.error('An error occurred while retrieving subscription details:', error);
throw new Error('Could not retrieve subscription details');
}
};
const getUserTransactions = async (userId: string) => {
try {
// Step 1: Retrieve the customer ID associated with the user
const { data, error } = await supabaseAdmin
.from('customers')
.select('stripe_customer_id')
.eq('id', userId)
.single();
if (error) {
console.error('Error retrieving user data:', error.message);
throw new Error('Failed to fetch user data');
}
const customerId = data?.stripe_customer_id;
if (!customerId) {
throw new Error('No Stripe customer ID found for this user');
}
// Step 2: Fetch charge transactions from Stripe
const charges = await stripeClient.charges.list({ customer: customerId });
// Step 3: Format transaction data
const transactions = charges.data.map((charge) => ({
amount: (charge.amount / 100).toFixed(2),
currency: charge.currency.toUpperCase(),
description: charge.description ?? 'No description provided',
status: charge.status,
created: new Date(charge.created * 1000).toLocaleDateString('en-GB', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
}),
receipt_url: charge.receipt_url
}));
return transactions;
} catch (error) {
console.error('An error occurred while retrieving transactions:', error);
throw new Error('Could not retrieve transactions');
}
};
export { export {
upsertProductRecord, upsertProductRecord,
upsertPriceRecord, upsertPriceRecord,
@@ -495,5 +599,7 @@ export {
getActiveProductsWithPrices, getActiveProductsWithPrices,
getProductById, getProductById,
upsertCustomerToSupabase, upsertCustomerToSupabase,
getStripeCustomerId getStripeCustomerId,
getUserSubscriptions,
getUserTransactions
}; };
@@ -0,0 +1,85 @@
<div class="container mx-auto p-6">
<h1 class="text-3xl font-bold text-primary mb-4">Cookie Policy</h1>
<p class="text-gray-700 mb-6">Effective Date: <span class="font-semibold">[Insert Date]</span></p>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">1. Introduction</h2>
<p class="text-gray-600">
This Cookie Policy explains how [Your Company/Website Name] ("we", "us", or "our") uses
cookies and similar technologies to collect and store information when you visit our website.
</p>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">2. What Are Cookies?</h2>
<p class="text-gray-600">
Cookies are small text files stored on your device (computer, tablet, smartphone) by your web
browser when you visit a website. They help us remember your preferences and improve your
experience on our website.
</p>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">3. Types of Cookies We Use</h2>
<ul class="list-disc pl-6 text-gray-600">
<li>
<span class="font-semibold">Essential Cookies</span>: These cookies are necessary for the
website to function properly and cannot be disabled in our systems. They include login
authentication and session management cookies.
</li>
<li>
<span class="font-semibold">Performance Cookies</span>: These cookies help us understand how
visitors interact with our website by collecting information on usage patterns, such as
pages visited and links clicked.
</li>
<li>
<span class="font-semibold">Functional Cookies</span>: These cookies allow us to remember
choices you make (such as your language preference) to provide a more personalised
experience.
</li>
<li>
<span class="font-semibold">Targeting/Advertising Cookies</span>: These cookies are used to
deliver relevant advertisements and track the effectiveness of our marketing campaigns.
</li>
</ul>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">4. How We Use Cookies</h2>
<p class="text-gray-600">
We use cookies to improve the functionality and performance of our website, analyse visitor
usage, and provide personalised content and advertisements. This helps us improve our website
and better understand our audience.
</p>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">5. Managing Your Cookie Preferences</h2>
<p class="text-gray-600">
You have the right to accept or reject cookies. You can manage your cookie preferences by
adjusting your browser settings. However, disabling certain cookies may affect your experience
on our website. For specific instructions on managing cookies, please consult your browser's
help documentation.
</p>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">6. Changes to Our Cookie Policy</h2>
<p class="text-gray-600">
We may update this Cookie Policy from time to time. Significant changes will be posted on this
page. We encourage you to review this policy periodically to stay informed about our use of
cookies.
</p>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">Contact Us</h2>
<p class="text-gray-600">
For questions about our Cookie Policy, please contact us at <a
href="mailto:your-email@example.com"
class="text-primary font-semibold">[Your Contact Email]</a
>
or visit our <a href="/contact" class="text-primary font-semibold">Contact Page</a>.
</p>
</section>
</div>
+125
View File
@@ -0,0 +1,125 @@
<div class="container mx-auto p-6">
<h1 class="text-3xl font-bold text-primary mb-4">Privacy Policy</h1>
<p class="text-gray-700 mb-6">Effective Date: <span class="font-semibold">[Insert Date]</span></p>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">1. Introduction</h2>
<p class="text-gray-600">
This Privacy Policy explains how we at [Your Company/Website Name] collect, use, and protect
your personal information when you visit our website. We are committed to safeguarding your
data and complying with relevant data protection laws, including the General Data Protection
Regulation (GDPR).
</p>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">2. Information We Collect</h2>
<p class="text-gray-600">
We collect personal and non-personal information to provide our services and enhance your
experience.
</p>
<ul class="list-disc pl-6 text-gray-600">
<li>
<span class="font-semibold">Personal Information</span>: Details like your name, email
address, and payment information collected when you make a purchase or create an account.
</li>
<li>
<span class="font-semibold">Usage Data</span>: Information such as IP address, browser type,
and browsing history for analytics and functionality.
</li>
</ul>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">3. How We Use Your Information</h2>
<ul class="list-disc pl-6 text-gray-600">
<li>To process orders and manage customer support.</li>
<li>To improve website functionality and provide a better user experience.</li>
<li>To send marketing emails, if you opt in (you may unsubscribe at any time).</li>
</ul>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">4. Sharing Information with Third Parties</h2>
<p class="text-gray-600">
We work with trusted third-party providers to offer secure services, such as payment
processing and authentication.
</p>
<div class="mt-4">
<h3 class="text-xl font-semibold">Stripe</h3>
<p class="text-gray-600">
For payment processing, we use <a
href="https://stripe.com"
target="_blank"
class="text-primary font-semibold">Stripe</a
>. Please refer to their
<a href="https://stripe.com/privacy" target="_blank" class="text-primary font-semibold"
>Privacy Policy</a
> for details.
</p>
</div>
<div class="mt-4">
<h3 class="text-xl font-semibold">Supabase</h3>
<p class="text-gray-600">
For database and authentication, we use <a
href="https://supabase.com"
target="_blank"
class="text-primary font-semibold">Supabase</a
>. See their
<a href="https://supabase.com/privacy" target="_blank" class="text-primary font-semibold"
>Privacy Policy</a
> for more information.
</p>
</div>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">5. Cookies and Tracking</h2>
<p class="text-gray-600">
We use cookies to provide essential site functionality, analytics, and improved user
experience. By continuing to use our site, you consent to our use of cookies. You can manage
your cookie preferences in your browser settings. For more details, please refer to our <a
href="/cookies"
class="text-primary font-semibold">Cookie Policy</a
>.
</p>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">6. Your Data Rights</h2>
<p class="text-gray-600">
Under GDPR, you have the right to access, correct, delete, and restrict the processing of your
personal data. To make a request, please contact us at <a
href="mailto:your-email@example.com"
class="text-primary font-semibold">[Your Contact Email]</a
>.
</p>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">7. Data Security</h2>
<p class="text-gray-600">
We take reasonable steps to protect your information through encryption and secure access
protocols. However, no online service can be 100% secure.
</p>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">8. Policy Updates</h2>
<p class="text-gray-600">
We may update this policy periodically. Significant changes will be posted on this page, so
please review it regularly.
</p>
</section>
<section class="mb-6">
<h2 class="text-2xl font-semibold mb-2">Contact Us</h2>
<p class="text-gray-600">
If you have questions about this Privacy Policy, please contact us at <a
href="mailto:your-email@example.com"
class="text-primary font-semibold">[Your Contact Email]</a
>
or visit our <a href="/contact" class="text-primary font-semibold">Contact Page</a>.
</p>
</section>
</div>
+5 -1
View File
@@ -195,8 +195,12 @@
d="M22.672 15.226l-2.432.811.841 2.515c.33 1.019-.209 2.127-1.23 2.456-1.15.325-2.148-.321-2.463-1.226l-.84-2.518-5.013 1.677.84 2.517c.391 1.203-.434 2.542-1.831 2.542-.88 0-1.601-.564-1.86-1.314l-.842-2.516-2.431.809c-1.135.328-2.145-.317-2.463-1.229-.329-1.018.211-2.127 1.231-2.456l2.432-.809-1.621-4.823-2.432.808c-1.355.384-2.558-.59-2.558-1.839 0-.817.509-1.582 1.327-1.846l2.433-.809-.842-2.515c-.33-1.02.211-2.129 1.232-2.458 1.02-.329 2.13.209 2.461 1.229l.842 2.515 5.011-1.677-.839-2.517c-.403-1.238.484-2.553 1.843-2.553.819 0 1.585.509 1.85 1.326l.841 2.517 2.431-.81c1.02-.33 2.131.211 2.461 1.229.332 1.018-.21 2.126-1.23 2.456l-2.433.809 1.622 4.823 2.433-.809c1.242-.401 2.557.484 2.557 1.838 0 .819-.51 1.583-1.328 1.847m-8.992-6.428l-5.01 1.675 1.619 4.828 5.011-1.674-1.62-4.829z" d="M22.672 15.226l-2.432.811.841 2.515c.33 1.019-.209 2.127-1.23 2.456-1.15.325-2.148-.321-2.463-1.226l-.84-2.518-5.013 1.677.84 2.517c.391 1.203-.434 2.542-1.831 2.542-.88 0-1.601-.564-1.86-1.314l-.842-2.516-2.431.809c-1.135.328-2.145-.317-2.463-1.229-.329-1.018.211-2.127 1.231-2.456l2.432-.809-1.621-4.823-2.432.808c-1.355.384-2.558-.59-2.558-1.839 0-.817.509-1.582 1.327-1.846l2.433-.809-.842-2.515c-.33-1.02.211-2.129 1.232-2.458 1.02-.329 2.13.209 2.461 1.229l.842 2.515 5.011-1.677-.839-2.517c-.403-1.238.484-2.553 1.843-2.553.819 0 1.585.509 1.85 1.326l.841 2.517 2.431-.81c1.02-.33 2.131.211 2.461 1.229.332 1.018-.21 2.126-1.23 2.456l-2.433.809 1.622 4.823 2.433-.809c1.242-.401 2.557.484 2.557 1.838 0 .819-.51 1.583-1.328 1.847m-8.992-6.428l-5.01 1.675 1.619 4.828 5.011-1.674-1.62-4.829z"
></path> ></path>
</svg> </svg>
<p>Copyright © 2024 - All right reserved</p> <p>Copyright © 2024 - All rights reserved</p>
</aside> </aside>
<div class="grid-flow-col gap-4">
<a href="/privacy">Privacy Policy</a>
<a href="/cookies">Cookie Policy</a>
</div>
<nav class="grid-flow-col gap-4 md:place-self-center md:justify-self-end"> <nav class="grid-flow-col gap-4 md:place-self-center md:justify-self-end">
<a href="/"> <a href="/">
<svg <svg
+13 -2
View File
@@ -1,5 +1,7 @@
import { fail, redirect } from '@sveltejs/kit'; import { fail, redirect } from '@sveltejs/kit';
import type { Actions, PageServerLoad } from './$types'; import type { Actions, PageServerLoad } from './$types';
import logger from '$lib/utils/logger/logger';
import { getUserSubscriptions, getUserTransactions } from '$lib/utils/supabase/admin';
export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => { export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => {
const { session } = await safeGetSession(); const { session } = await safeGetSession();
@@ -14,7 +16,11 @@ export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession
.eq('id', session.user.id) .eq('id', session.user.id)
.single(); .single();
return { session, user }; const subscriptions = await getUserSubscriptions(session.user.id);
const transactions = await getUserTransactions(session.user.id);
return { session, user, subscriptions, transactions };
}; };
export const actions: Actions = { export const actions: Actions = {
@@ -34,7 +40,12 @@ export const actions: Actions = {
}) })
.eq('id', session?.user.id); .eq('id', session?.user.id);
console.error(error); if (error) {
logger.error('Failed to update user name', {
userId: session?.user.id,
error
});
}
if (error) { if (error) {
return fail(500, { return fail(500, {
+142 -20
View File
@@ -5,7 +5,7 @@
export let data; export let data;
export let form; export let form;
let { session, supabase, user } = data; let { session, user, subscriptions, transactions } = data;
$: ({ session, supabase, user } = data); $: ({ session, supabase, user } = data);
let profileForm: HTMLFormElement; let profileForm: HTMLFormElement;
@@ -20,26 +20,148 @@
}; };
</script> </script>
<div class="form-widget"> <div
<form class="account-page max-w-4xl mx-auto p-6 bg-base-100 rounded-lg shadow-md space-y-6 overflow-y-auto"
class="form-widget" >
method="post" <!-- Flex container for two columns -->
action="?/update" <div class="flex flex-col lg:flex-row gap-6">
use:enhance={handleSubmit} <!-- Left Column: Account Overview, Subscriptions, and Billing History -->
bind:this={profileForm} <div class="flex-1 space-y-6">
> <!-- Account Overview -->
<div> <section class="space-y-4">
<label for="name">Name</label> <h2 class="text-xl font-semibold">Account Overview</h2>
<input id="name" name="name" type="text" value={form?.name ?? name} /> <div class="form-control">
<label for="email" class="label font-medium">Email</label>
<input
id="email"
type="email"
class="input input-bordered"
value={session.user.email}
disabled
/>
</div>
<div class="text-sm text-gray-500">Signed in with magic link.</div>
<form
class="form-widget space-y-4"
method="post"
action="?/update"
use:enhance={handleSubmit}
bind:this={profileForm}
>
<div class="form-control">
<label for="name" class="label font-medium">Name</label>
<input
id="name"
name="name"
type="text"
value={form?.name ?? name}
class="input input-bordered w-full"
placeholder="Enter your name"
/>
</div>
<div>
<input
type="submit"
class="btn btn-primary w-full"
value={loading ? 'Loading...' : 'Update Name'}
disabled={loading}
/>
</div>
</form>
</section>
<!-- Active Subscriptions -->
<section class="space-y-4">
<h2 class="text-xl font-semibold">Subscriptions</h2>
{#if subscriptions.length === 0}
<p class="text-gray-600">You do not have any subscriptions.</p>
{:else}
<ul class="space-y-3">
{#each subscriptions as subscription}
<li class="p-4 border border-gray-200 rounded-md">
<div class="flex items-center justify-between">
<div>
<p class="font-semibold">{subscription.productName}</p>
<p class="text-sm text-gray-500">
Price: {subscription.amount}
{subscription.currency} / {subscription.interval}
</p>
<p class="text-sm text-gray-500">
Expiry Date: {subscription.expiryDate}
</p>
<p class="text-sm text-gray-500">
Status: {subscription.status}
</p>
</div>
<!-- <button class="btn btn-secondary btn-sm">Manage</button> -->
</div>
</li>
{/each}
</ul>
{/if}
</section>
<!-- Billing History -->
<section class="space-y-4">
<h2 class="text-xl font-semibold">Billing History</h2>
<ul class="space-y-3">
{#if transactions}
{#each transactions as transaction}
<li class="flex justify-between p-4 border border-gray-200 rounded-md">
<div>
<p class="text-sm text-gray-500">Date: {transaction.created}</p>
<p class="text-sm text-gray-500">
Amount: {transaction.amount}
{transaction.currency}
</p>
</div>
{#if transaction.receipt_url}
<a href={transaction.receipt_url} class="btn btn-outline btn-sm" target="_blank">
Download Receipt
</a>
{:else}
<span class="text-gray-500">Receipt unavailable</span>
{/if}
</li>
{/each}
{/if}
</ul>
</section>
</div> </div>
<div> <!-- Right Column: Notification Preferences and Feedback -->
<input <div class="flex-none w-full lg:w-64 space-y-6">
type="submit" <!-- Notification Preferences -->
class="button block primary" <section class="space-y-4">
value={loading ? 'Loading...' : 'Update'} <h2 class="text-xl font-semibold">Notification Preferences</h2>
disabled={loading} <form method="post" action="?/update_notifications" class="space-y-2">
/> <label class="flex items-center space-x-3">
<input type="checkbox" class="checkbox checkbox-primary" />
<!-- checked={user.notifications.productUpdates} -->
<span>Product Updates</span>
</label>
<label class="flex items-center space-x-3">
<input type="checkbox" class="checkbox checkbox-primary" />
<!-- checked={user.notifications.billingReminders} -->
<span>Billing Reminders</span>
</label>
<label class="flex items-center space-x-3">
<input type="checkbox" class="checkbox checkbox-primary" />
<!-- checked={user.notifications.promotions} -->
<span>Promotional Emails</span>
</label>
</form>
</section>
<!-- Feedback Section -->
<section class="space-y-4">
<h2 class="text-xl font-semibold">Feedback</h2>
<p class="text-gray-600">
We'd love to hear your thoughts! Please feel free to provide any feedback.
</p>
<a href="/contact" class="btn btn-outline w-full">Provide Feedback</a>
</section>
</div> </div>
</form> </div>
</div> </div>
+2 -1
View File
@@ -2,6 +2,7 @@ import { type RequestHandler, redirect } from '@sveltejs/kit';
import { stripe as stripeClient } from '$lib/utils/stripe'; import { stripe as stripeClient } from '$lib/utils/stripe';
import { getStripeCustomerId } from '$lib/utils/supabase/admin'; import { getStripeCustomerId } from '$lib/utils/supabase/admin';
import type Stripe from 'stripe'; import type Stripe from 'stripe';
import logger from '$lib/utils/logger/logger';
// Create a checkout session // Create a checkout session
export const POST: RequestHandler = async ({ request, cookies, locals: { safeGetSession } }) => { export const POST: RequestHandler = async ({ request, cookies, locals: { safeGetSession } }) => {
@@ -34,7 +35,7 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
try { try {
stripeCustomerId = await getStripeCustomerId(userEmail!, userId); stripeCustomerId = await getStripeCustomerId(userEmail!, userId);
} catch (error) { } catch (error) {
console.error('Error fetching Stripe customer ID:', error); logger.error('Error fetching Stripe customer ID:', error);
} }
const stripeCheckoutSessionObject = { const stripeCheckoutSessionObject = {
+2 -1
View File
@@ -2,6 +2,7 @@ import { type RequestHandler, redirect } from '@sveltejs/kit';
import { stripe as stripeClient } from '$lib/utils/stripe'; import { stripe as stripeClient } from '$lib/utils/stripe';
import { getStripeCustomerId } from '$lib/utils/supabase/admin'; import { getStripeCustomerId } from '$lib/utils/supabase/admin';
import type Stripe from 'stripe'; import type Stripe from 'stripe';
import logger from '$lib/utils/logger/logger';
// Create a subscription checkout session // Create a subscription checkout session
export const POST: RequestHandler = async ({ request, cookies, locals: { safeGetSession } }) => { export const POST: RequestHandler = async ({ request, cookies, locals: { safeGetSession } }) => {
@@ -29,7 +30,7 @@ export const POST: RequestHandler = async ({ request, cookies, locals: { safeGet
try { try {
stripeCustomerId = await getStripeCustomerId(userEmail!, userId); stripeCustomerId = await getStripeCustomerId(userEmail!, userId);
} catch (error) { } catch (error) {
console.error('Error fetching Stripe customer ID:', error); logger.error('Error fetching Stripe customer ID:', error);
} }
const stripeCheckoutSessionObject = { const stripeCheckoutSessionObject = {
+3 -2
View File
@@ -10,6 +10,7 @@ import {
recordProductPurchase recordProductPurchase
} from '$lib/utils/supabase/admin'; } from '$lib/utils/supabase/admin';
import { stripe as stripeClient } from '$lib/utils/stripe'; import { stripe as stripeClient } from '$lib/utils/stripe';
import logger from '$lib/utils/logger/logger';
export const POST: RequestHandler = async ({ request }) => { export const POST: RequestHandler = async ({ request }) => {
const body = await request.text(); const body = await request.text();
@@ -26,7 +27,7 @@ export const POST: RequestHandler = async ({ request }) => {
try { try {
event = stripeClient.webhooks.constructEvent(body, signature, STRIPE_ENDPOINT_SECRET); event = stripeClient.webhooks.constructEvent(body, signature, STRIPE_ENDPOINT_SECRET);
} catch (err) { } catch (err) {
console.error(`⚠️ Webhook signature verification failed.`, err.message); logger.error(`⚠️ Webhook signature verification failed.`, err.message);
return { return {
status: 400, status: 400,
body: {} body: {}
@@ -104,7 +105,7 @@ export const POST: RequestHandler = async ({ request }) => {
break; break;
} }
default: default:
console.log(`Unhandled event type ${event.type}`); logger.info(`Unhandled event type ${event.type}`);
} }
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : 'An unknown error has occurred'; const message = err instanceof Error ? err.message : 'An unknown error has occurred';
+3 -2
View File
@@ -1,13 +1,14 @@
import { redirect } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit';
import Stripe from 'stripe'; import Stripe from 'stripe';
import { getProductById, upsertCustomerToSupabase } from '$lib/utils/supabase/admin'; import { getProductById, upsertCustomerToSupabase } from '$lib/utils/supabase/admin';
import logger from '$lib/utils/logger/logger';
export const load = async ({ fetch, locals: { safeGetSession } }) => { export const load = async ({ fetch, locals: { safeGetSession } }) => {
const { session } = await safeGetSession(); const { session } = await safeGetSession();
const response = await fetch('/api/checkout/status'); const response = await fetch('/api/checkout/status');
if (!response.ok) { if (!response.ok) {
console.error('Failed to fetch checkout status'); logger.error('Failed to fetch checkout status');
return; return;
} }
@@ -30,7 +31,7 @@ export const load = async ({ fetch, locals: { safeGetSession } }) => {
const stripeCustomerId = checkoutSession.customer as string; const stripeCustomerId = checkoutSession.customer as string;
await upsertCustomerToSupabase(session.user.id, stripeCustomerId); await upsertCustomerToSupabase(session.user.id, stripeCustomerId);
} else { } else {
console.error('User ID is undefined'); logger.error('User ID is undefined');
} }
return { return {
+26 -8
View File
@@ -1,19 +1,37 @@
import { json } from '@sveltejs/kit'; import { json } from '@sveltejs/kit';
import { getActiveProductsWithPrices } from '$lib/utils/supabase/admin'; import { getActiveProductsWithPrices } from '$lib/utils/supabase/admin';
import logger from '$lib/utils/logger/logger';
export const GET = async ({ url }) => { export const GET = async ({ url }) => {
const limitParam: string | null = url.searchParams.get('limit');
const offsetParam: string | null = url.searchParams.get('offset');
try { try {
const limitParam = url.searchParams.get('limit'); const limit = limitParam ? Number.parseInt(limitParam, 10) : 10;
const offsetParam = url.searchParams.get('offset'); const offset = offsetParam ? Number.parseInt(offsetParam, 10) : 0;
const limit = limitParam ? parseInt(limitParam, 10) : 10;
const offset = offsetParam ? parseInt(offsetParam, 10) : 0;
const { products, count } = await getActiveProductsWithPrices(limit, offset); const { products, count } = await getActiveProductsWithPrices(limit, offset);
return json({ products, count }); return json({ products, count });
} catch (error) { } catch (error: unknown) {
console.error(error); if (error instanceof Error) {
return json({ error: error.message }, { status: 500 }); logger.error('Failed to fetch products', {
error: error.message,
stack: error.stack,
params: {
limit: limitParam,
offset: offsetParam
}
});
return json({ error: error.message }, { status: 500 });
} else {
logger.error('Failed to fetch products', {
error,
params: {
limit: limitParam,
offset: offsetParam
}
});
return json({ error }, { status: 500 });
}
} }
}; };