diff options
| author | Adrien Hopkins <[email protected]> | 2022-08-02 13:13:27 -0500 |
|---|---|---|
| committer | Adrien Hopkins <[email protected]> | 2022-08-02 13:13:27 -0500 |
| commit | c21ee41b206db79b2157d8743b0473a094448dfb (patch) | |
| tree | fac1a595e451c0b32ceedfe4fb70855270456377 | |
| parent | 3b216dd6c170a7d71d21a391098c554008c8bdb4 (diff) | |
| parent | 05f5001fc7d325e31d9c777be11343604c72fbfd (diff) | |
| download | coinos-ui-c21ee41b206db79b2157d8743b0473a094448dfb.tar.gz coinos-ui-c21ee41b206db79b2157d8743b0473a094448dfb.zip | |
Merge branch 'master' into i18n
62 files changed, 1555 insertions, 513 deletions
@@ -1 +1,2 @@ VITE_COINOS_URL=http://localhost:3119 +VITE_COINOS_TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImJvYiIsImlhdCI6MTY1ODg1NTM4Mn0.typE3V7Jb29S3aXGKd81RaazR6I4CBG_oW5WAY3avr4 diff --git a/package.json b/package.json index 89aadbfa..a28cbd08 100644 --- a/package.json +++ b/package.json @@ -13,15 +13,17 @@ "devDependencies": { "@sveltejs/adapter-auto": "next", "@sveltejs/kit": "next", + "@zerodevx/svelte-toast": "^0.7.2", "autoprefixer": "^10.4.7", - "got": "^12.2.0", + "cookie": "^0.5.0", "postcss": "^8.4.14", - "prettier": "^2.6.2", + "prettier": "^2.7.1", "prettier-plugin-svelte": "^2.7.0", - "svelte": "^3.44.0", + "qr-code-styling": "1.6.0-rc.1", + "svelte": "^3.49.0", "svelte-preprocess": "^4.10.7", "tailwindcss": "^3.1.6", - "vite": "^3.0.0" + "vite": "^3.0.3" }, "type": "module", "dependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc6a7109..047a437d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3,32 +3,36 @@ lockfileVersion: 5.4 specifiers: '@sveltejs/adapter-auto': next '@sveltejs/kit': next + '@zerodevx/svelte-toast': ^0.7.2 autoprefixer: ^10.4.7 - got: ^12.2.0 + cookie: ^0.5.0 postcss: ^8.4.14 - prettier: ^2.6.2 + prettier: ^2.7.1 prettier-plugin-svelte: ^2.7.0 - svelte: ^3.44.0 + qr-code-styling: 1.6.0-rc.1 + svelte: ^3.49.0 svelte-i18n: ^3.4.0 svelte-preprocess: ^4.10.7 tailwindcss: ^3.1.6 - vite: ^3.0.0 + vite: ^3.0.3 dependencies: svelte-i18n: [email protected] devDependencies: '@sveltejs/adapter-auto': 1.0.0-next.64 - '@sveltejs/kit': [email protected][email protected] + '@sveltejs/kit': [email protected][email protected] + '@zerodevx/svelte-toast': 0.7.2 autoprefixer: [email protected] - got: 12.2.0 + cookie: 0.5.0 postcss: 8.4.14 prettier: 2.7.1 prettier-plugin-svelte: 2.7.0_o3ioganyptcsrh6x4hnxvjkpqi + qr-code-styling: 1.6.0-rc.1 svelte: 3.49.0 svelte-preprocess: 4.10.7_nxvsp6sjiltnatqa6jdm4mr6zu tailwindcss: 3.1.6 - vite: 3.0.2 + vite: 3.0.3 packages: @@ -121,11 +125,6 @@ packages: picomatch: 2.3.1 dev: true - /@sindresorhus/is/5.3.0: - resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==} - engines: {node: '>=14.16'} - dev: true - /@sveltejs/adapter-auto/1.0.0-next.64: resolution: {integrity: sha512-Q8DwcS6wl1GovzS9JJzaD/WL/Lfk1ur4nAF1HtmsUvZDpsPBVDqnK2AhYU4G3oFNiuHstrjAogMy5th8ptSFGw==} dependencies: @@ -141,7 +140,7 @@ packages: resolution: {integrity: sha512-HhEFZP72GJ8AZGgFECKIiayDcLaAWi65pI0AnBfiNhCifYSlH/mPNWNVD4AWRDnXnH6XU+FLwhGDnIDwytTyYg==} dependencies: '@cloudflare/workers-types': 3.14.1 - esbuild: 0.14.49 + esbuild: 0.14.50 worktop: 0.8.0-next.14 dev: true @@ -149,8 +148,8 @@ packages: resolution: {integrity: sha512-la1CGtWO1xul1L3zEoFAoc4EX2uxZjrZcOMS3tkKB8drxhbQsNbnTE6fmSSMFiZXhxaikczrBgQwqIaDkLTmZg==} dependencies: '@iarna/toml': 2.2.5 - esbuild: 0.14.49 - set-cookie-parser: 2.5.0 + esbuild: 0.14.50 + set-cookie-parser: 2.5.1 tiny-glob: 0.2.9 dev: true @@ -158,31 +157,31 @@ packages: resolution: {integrity: sha512-s3Hcxu9nCG/rR3C3cFbdQGjTa5W4K2kRcc6S5Xefx7itbrw+4v3KpO8ZPB6qM55XDwVxuG7260NMHVI6MUGmSA==} dependencies: '@vercel/nft': 0.21.0 - esbuild: 0.14.49 + esbuild: 0.14.50 transitivePeerDependencies: - encoding - supports-color dev: true - /@sveltejs/kit/[email protected][email protected]: - resolution: {integrity: sha512-o8sFZVxUNTwLPs2ZwrKSyZfbU/VIRq2NnaQQ9JcSzr0Bs+M09rrjTuSpFy0sXUCZkBh3oVWFgrVqABwEOEHoSQ==} + /@sveltejs/kit/[email protected][email protected]: + resolution: {integrity: sha512-ONjHoRurnFUe+0lv0P9rZQpDuuJy4Cr/lDneoG+lkOR5/7gcvd03TlVdxLz1ZQAWPbgovm/PpshOvIUeC4n62A==} engines: {node: '>=16.9'} hasBin: true peerDependencies: svelte: ^3.44.0 vite: ^3.0.0 dependencies: - '@sveltejs/vite-plugin-svelte': [email protected][email protected] + '@sveltejs/vite-plugin-svelte': [email protected][email protected] chokidar: 3.5.3 sade: 1.8.1 svelte: 3.49.0 - vite: 3.0.2 + vite: 3.0.3 transitivePeerDependencies: - diff-match-patch - supports-color dev: true - /@sveltejs/vite-plugin-svelte/[email protected][email protected]: + /@sveltejs/vite-plugin-svelte/[email protected][email protected]: resolution: {integrity: sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==} engines: {node: ^14.18.0 || >= 16} peerDependencies: @@ -200,59 +199,23 @@ packages: magic-string: 0.26.2 svelte: 3.49.0 svelte-hmr: [email protected] - vite: 3.0.2 + vite: 3.0.3 transitivePeerDependencies: - supports-color dev: true - /@szmarczak/http-timer/5.0.1: - resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} - engines: {node: '>=14.16'} - dependencies: - defer-to-connect: 2.0.1 - dev: true - - /@types/cacheable-request/6.0.2: - resolution: {integrity: sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==} - dependencies: - '@types/http-cache-semantics': 4.0.1 - '@types/keyv': 3.1.4 - '@types/node': 18.0.6 - '@types/responselike': 1.0.0 - dev: true - - /@types/http-cache-semantics/4.0.1: - resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} - dev: true - - /@types/json-buffer/3.0.0: - resolution: {integrity: sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==} - dev: true - - /@types/keyv/3.1.4: - resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - dependencies: - '@types/node': 18.0.6 - dev: true - - /@types/node/18.0.6: - resolution: {integrity: sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==} + /@types/node/18.6.1: + resolution: {integrity: sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg==} dev: true /@types/pug/2.0.6: resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} dev: true - /@types/responselike/1.0.0: - resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} - dependencies: - '@types/node': 18.0.6 - dev: true - /@types/sass/1.43.1: resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==} dependencies: - '@types/node': 18.0.6 + '@types/node': 18.6.1 dev: true /@vercel/nft/0.21.0: @@ -260,7 +223,7 @@ packages: hasBin: true dependencies: '@mapbox/node-pre-gyp': 1.0.9 - acorn: 8.7.1 + acorn: 8.8.0 async-sema: 3.1.1 bindings: 1.5.0 estree-walker: 2.0.2 @@ -275,6 +238,10 @@ packages: - supports-color dev: true + /@zerodevx/svelte-toast/0.7.2: + resolution: {integrity: sha512-vWiY6IqsstcOoQ8PFBuFuxgPkj1JFAGhUF9gC7wLx7c5A9SSfdtxWs/39ekGSIeyJK0yqWhTcmzGrCEWSELzDw==} + dev: true + /abbrev/1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} dev: true @@ -298,8 +265,8 @@ packages: hasBin: true dev: true - /acorn/8.7.1: - resolution: {integrity: sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==} + /acorn/8.8.0: + resolution: {integrity: sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -354,7 +321,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.2 - caniuse-lite: 1.0.30001367 + caniuse-lite: 1.0.30001370 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -396,8 +363,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001367 - electron-to-chromium: 1.4.195 + caniuse-lite: 1.0.30001370 + electron-to-chromium: 1.4.202 node-releases: 2.0.6 update-browserslist-db: [email protected] dev: true @@ -406,31 +373,13 @@ packages: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} dev: true - /cacheable-lookup/6.0.4: - resolution: {integrity: sha512-mbcDEZCkv2CZF4G01kr8eBd/5agkt9oCqz75tJMSIsquvRZ2sL6Hi5zGVKi/0OSC9oO1GHfJ2AV0ZIOY9vye0A==} - engines: {node: '>=10.6.0'} - dev: true - - /cacheable-request/7.0.2: - resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==} - engines: {node: '>=8'} - dependencies: - clone-response: 1.0.3 - get-stream: 5.2.0 - http-cache-semantics: 4.1.0 - keyv: 4.3.3 - lowercase-keys: 2.0.0 - normalize-url: 6.1.0 - responselike: 2.0.1 - dev: true - /camelcase-css/2.0.1: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} dev: true - /caniuse-lite/1.0.30001367: - resolution: {integrity: sha512-XDgbeOHfifWV3GEES2B8rtsrADx4Jf+juKX2SICJcaUhjYBO3bR96kvEIHa15VU6ohtOhBZuPGGYGbXMRn0NCw==} + /caniuse-lite/1.0.30001370: + resolution: {integrity: sha512-3PDmaP56wz/qz7G508xzjx8C+MC2qEm4SYhSEzC9IBROo+dGXFWRuaXkWti0A9tuI00g+toiriVqxtWMgl350g==} dev: true /chokidar/3.5.3: @@ -453,12 +402,6 @@ packages: engines: {node: '>=10'} dev: true - /clone-response/1.0.3: - resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} - dependencies: - mimic-response: 1.0.1 - dev: true - /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true @@ -468,14 +411,6 @@ packages: hasBin: true dev: true - /compress-brotli/1.3.8: - resolution: {integrity: sha512-lVcQsjhxhIXsuupfy9fmZUFtAIdBmXA7EGY6GBdgZ++qkM9zG4YFT8iU7FoBxzryNDMOpD1HIFHUSX4D87oqhQ==} - engines: {node: '>= 12'} - dependencies: - '@types/json-buffer': 3.0.0 - json-buffer: 3.0.1 - dev: true - /concat-map/0.0.1: resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} dev: true @@ -484,6 +419,11 @@ packages: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} dev: true + /cookie/0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: true + /cssesc/3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -502,22 +442,10 @@ packages: ms: 2.1.2 dev: true - /decompress-response/6.0.0: - resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} - engines: {node: '>=10'} - dependencies: - mimic-response: 3.1.0 - dev: true - /deepmerge/4.2.2: resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==} engines: {node: '>=0.10.0'} - /defer-to-connect/2.0.1: - resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} - engines: {node: '>=10'} - dev: true - /defined/1.0.0: resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==} dev: true @@ -554,26 +482,20 @@ packages: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} dev: true - /electron-to-chromium/1.4.195: - resolution: {integrity: sha512-vefjEh0sk871xNmR5whJf9TEngX+KTKS3hOHpjoMpauKkwlGwtMz1H8IaIjAT/GNnX0TbGwAdmVoXCAzXf+PPg==} + /electron-to-chromium/1.4.202: + resolution: {integrity: sha512-JYsK2ex9lmQD27kj19fhXYxzFJ/phLAkLKHv49A5UY6kMRV2xED3qMMLg/voW/+0AR6wMiI+VxlmK9NDtdxlPA==} dev: true /emoji-regex/8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} dev: true - /end-of-stream/1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - dependencies: - once: 1.4.0 - dev: true - /es6-promise/3.3.1: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} dev: true - /esbuild-android-64/0.14.49: - resolution: {integrity: sha512-vYsdOTD+yi+kquhBiFWl3tyxnj2qZJsl4tAqwhT90ktUdnyTizgle7TjNx6Ar1bN7wcwWqZ9QInfdk2WVagSww==} + /esbuild-android-64/0.14.50: + resolution: {integrity: sha512-H7iUEm7gUJHzidsBlFPGF6FTExazcgXL/46xxLo6i6bMtPim6ZmXyTccS8yOMpy6HAC6dPZ/JCQqrkkin69n6Q==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -581,8 +503,8 @@ packages: dev: true optional: true - /esbuild-android-arm64/0.14.49: - resolution: {integrity: sha512-g2HGr/hjOXCgSsvQZ1nK4nW/ei8JUx04Li74qub9qWrStlysaVmadRyTVuW32FGIpLQyc5sUjjZopj49eGGM2g==} + /esbuild-android-arm64/0.14.50: + resolution: {integrity: sha512-NFaoqEwa+OYfoYVpQWDMdKII7wZZkAjtJFo1WdnBeCYlYikvUhTnf2aPwPu5qEAw/ie1NYK0yn3cafwP+kP+OQ==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -590,8 +512,8 @@ packages: dev: true optional: true - /esbuild-darwin-64/0.14.49: - resolution: {integrity: sha512-3rvqnBCtX9ywso5fCHixt2GBCUsogNp9DjGmvbBohh31Ces34BVzFltMSxJpacNki96+WIcX5s/vum+ckXiLYg==} + /esbuild-darwin-64/0.14.50: + resolution: {integrity: sha512-gDQsCvGnZiJv9cfdO48QqxkRV8oKAXgR2CGp7TdIpccwFdJMHf8hyIJhMW/05b/HJjET/26Us27Jx91BFfEVSA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -599,8 +521,8 @@ packages: dev: true optional: true - /esbuild-darwin-arm64/0.14.49: - resolution: {integrity: sha512-XMaqDxO846srnGlUSJnwbijV29MTKUATmOLyQSfswbK/2X5Uv28M9tTLUJcKKxzoo9lnkYPsx2o8EJcTYwCs/A==} + /esbuild-darwin-arm64/0.14.50: + resolution: {integrity: sha512-36nNs5OjKIb/Q50Sgp8+rYW/PqirRiFN0NFc9hEvgPzNJxeJedktXwzfJSln4EcRFRh5Vz4IlqFRScp+aiBBzA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -608,8 +530,8 @@ packages: dev: true optional: true - /esbuild-freebsd-64/0.14.49: - resolution: {integrity: sha512-NJ5Q6AjV879mOHFri+5lZLTp5XsO2hQ+KSJYLbfY9DgCu8s6/Zl2prWXVANYTeCDLlrIlNNYw8y34xqyLDKOmQ==} + /esbuild-freebsd-64/0.14.50: + resolution: {integrity: sha512-/1pHHCUem8e/R86/uR+4v5diI2CtBdiWKiqGuPa9b/0x3Nwdh5AOH7lj+8823C6uX1e0ufwkSLkS+aFZiBCWxA==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -617,8 +539,8 @@ packages: dev: true optional: true - /esbuild-freebsd-arm64/0.14.49: - resolution: {integrity: sha512-lFLtgXnAc3eXYqj5koPlBZvEbBSOSUbWO3gyY/0+4lBdRqELyz4bAuamHvmvHW5swJYL7kngzIZw6kdu25KGOA==} + /esbuild-freebsd-arm64/0.14.50: + resolution: {integrity: sha512-iKwUVMQztnPZe5pUYHdMkRc9aSpvoV1mkuHlCoPtxZA3V+Kg/ptpzkcSY+fKd0kuom+l6Rc93k0UPVkP7xoqrw==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -626,8 +548,8 @@ packages: dev: true optional: true - /esbuild-linux-32/0.14.49: - resolution: {integrity: sha512-zTTH4gr2Kb8u4QcOpTDVn7Z8q7QEIvFl/+vHrI3cF6XOJS7iEI1FWslTo3uofB2+mn6sIJEQD9PrNZKoAAMDiA==} + /esbuild-linux-32/0.14.50: + resolution: {integrity: sha512-sWUwvf3uz7dFOpLzYuih+WQ7dRycrBWHCdoXJ4I4XdMxEHCECd8b7a9N9u7FzT6XR2gHPk9EzvchQUtiEMRwqw==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -635,8 +557,8 @@ packages: dev: true optional: true - /esbuild-linux-64/0.14.49: - resolution: {integrity: sha512-hYmzRIDzFfLrB5c1SknkxzM8LdEUOusp6M2TnuQZJLRtxTgyPnZZVtyMeCLki0wKgYPXkFsAVhi8vzo2mBNeTg==} + /esbuild-linux-64/0.14.50: + resolution: {integrity: sha512-u0PQxPhaeI629t4Y3EEcQ0wmWG+tC/LpP2K7yDFvwuPq0jSQ8SIN+ARNYfRjGW15O2we3XJvklbGV0wRuUCPig==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -644,8 +566,8 @@ packages: dev: true optional: true - /esbuild-linux-arm/0.14.49: - resolution: {integrity: sha512-iE3e+ZVv1Qz1Sy0gifIsarJMQ89Rpm9mtLSRtG3AH0FPgAzQ5Z5oU6vYzhc/3gSPi2UxdCOfRhw2onXuFw/0lg==} + /esbuild-linux-arm/0.14.50: + resolution: {integrity: sha512-VALZq13bhmFJYFE/mLEb+9A0w5vo8z+YDVOWeaf9vOTrSC31RohRIwtxXBnVJ7YKLYfEMzcgFYf+OFln3Y0cWg==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -653,8 +575,8 @@ packages: dev: true optional: true - /esbuild-linux-arm64/0.14.49: - resolution: {integrity: sha512-KLQ+WpeuY+7bxukxLz5VgkAAVQxUv67Ft4DmHIPIW+2w3ObBPQhqNoeQUHxopoW/aiOn3m99NSmSV+bs4BSsdA==} + /esbuild-linux-arm64/0.14.50: + resolution: {integrity: sha512-ZyfoNgsTftD7Rp5S7La5auomKdNeB3Ck+kSKXC4pp96VnHyYGjHHXWIlcbH8i+efRn9brszo1/Thl1qn8RqmhQ==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -662,8 +584,8 @@ packages: dev: true optional: true - /esbuild-linux-mips64le/0.14.49: - resolution: {integrity: sha512-n+rGODfm8RSum5pFIqFQVQpYBw+AztL8s6o9kfx7tjfK0yIGF6tm5HlG6aRjodiiKkH2xAiIM+U4xtQVZYU4rA==} + /esbuild-linux-mips64le/0.14.50: + resolution: {integrity: sha512-ygo31Vxn/WrmjKCHkBoutOlFG5yM9J2UhzHb0oWD9O61dGg+Hzjz9hjf5cmM7FBhAzdpOdEWHIrVOg2YAi6rTw==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -671,8 +593,8 @@ packages: dev: true optional: true - /esbuild-linux-ppc64le/0.14.49: - resolution: {integrity: sha512-WP9zR4HX6iCBmMFH+XHHng2LmdoIeUmBpL4aL2TR8ruzXyT4dWrJ5BSbT8iNo6THN8lod6GOmYDLq/dgZLalGw==} + /esbuild-linux-ppc64le/0.14.50: + resolution: {integrity: sha512-xWCKU5UaiTUT6Wz/O7GKP9KWdfbsb7vhfgQzRfX4ahh5NZV4ozZ4+SdzYG8WxetsLy84UzLX3Pi++xpVn1OkFQ==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -680,8 +602,8 @@ packages: dev: true optional: true - /esbuild-linux-riscv64/0.14.49: - resolution: {integrity: sha512-h66ORBz+Dg+1KgLvzTVQEA1LX4XBd1SK0Fgbhhw4akpG/YkN8pS6OzYI/7SGENiN6ao5hETRDSkVcvU9NRtkMQ==} + /esbuild-linux-riscv64/0.14.50: + resolution: {integrity: sha512-0+dsneSEihZTopoO9B6Z6K4j3uI7EdxBP7YSF5rTwUgCID+wHD3vM1gGT0m+pjCW+NOacU9kH/WE9N686FHAJg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -689,8 +611,8 @@ packages: dev: true optional: true - /esbuild-linux-s390x/0.14.49: - resolution: {integrity: sha512-DhrUoFVWD+XmKO1y7e4kNCqQHPs6twz6VV6Uezl/XHYGzM60rBewBF5jlZjG0nCk5W/Xy6y1xWeopkrhFFM0sQ==} + /esbuild-linux-s390x/0.14.50: + resolution: {integrity: sha512-tVjqcu8o0P9H4StwbIhL1sQYm5mWATlodKB6dpEZFkcyTI8kfIGWiWcrGmkNGH2i1kBUOsdlBafPxR3nzp3TDA==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -698,8 +620,8 @@ packages: dev: true optional: true - /esbuild-netbsd-64/0.14.49: - resolution: {integrity: sha512-BXaUwFOfCy2T+hABtiPUIpWjAeWK9P8O41gR4Pg73hpzoygVGnj0nI3YK4SJhe52ELgtdgWP/ckIkbn2XaTxjQ==} + /esbuild-netbsd-64/0.14.50: + resolution: {integrity: sha512-0R/glfqAQ2q6MHDf7YJw/TulibugjizBxyPvZIcorH0Mb7vSimdHy0XF5uCba5CKt+r4wjax1mvO9lZ4jiAhEg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -707,8 +629,8 @@ packages: dev: true optional: true - /esbuild-openbsd-64/0.14.49: - resolution: {integrity: sha512-lP06UQeLDGmVPw9Rg437Btu6J9/BmyhdoefnQ4gDEJTtJvKtQaUcOQrhjTq455ouZN4EHFH1h28WOJVANK41kA==} + /esbuild-openbsd-64/0.14.50: + resolution: {integrity: sha512-7PAtmrR5mDOFubXIkuxYQ4bdNS6XCK8AIIHUiZxq1kL8cFIH5731jPcXQ4JNy/wbj1C9sZ8rzD8BIM80Tqk29w==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -716,8 +638,8 @@ packages: dev: true optional: true - /esbuild-sunos-64/0.14.49: - resolution: {integrity: sha512-4c8Zowp+V3zIWje329BeLbGh6XI9c/rqARNaj5yPHdC61pHI9UNdDxT3rePPJeWcEZVKjkiAS6AP6kiITp7FSw==} + /esbuild-sunos-64/0.14.50: + resolution: {integrity: sha512-gBxNY/wyptvD7PkHIYcq7se6SQEXcSC8Y7mE0FJB+CGgssEWf6vBPfTTZ2b6BWKnmaP6P6qb7s/KRIV5T2PxsQ==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -725,8 +647,8 @@ packages: dev: true optional: true - /esbuild-windows-32/0.14.49: - resolution: {integrity: sha512-q7Rb+J9yHTeKr9QTPDYkqfkEj8/kcKz9lOabDuvEXpXuIcosWCJgo5Z7h/L4r7rbtTH4a8U2FGKb6s1eeOHmJA==} + /esbuild-windows-32/0.14.50: + resolution: {integrity: sha512-MOOe6J9cqe/iW1qbIVYSAqzJFh0p2LBLhVUIWdMVnNUNjvg2/4QNX4oT4IzgDeldU+Bym9/Tn6+DxvUHJXL5Zw==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -734,8 +656,8 @@ packages: dev: true optional: true - /esbuild-windows-64/0.14.49: - resolution: {integrity: sha512-+Cme7Ongv0UIUTniPqfTX6mJ8Deo7VXw9xN0yJEN1lQMHDppTNmKwAM3oGbD/Vqff+07K2gN0WfNkMohmG+dVw==} + /esbuild-windows-64/0.14.50: + resolution: {integrity: sha512-r/qE5Ex3w1jjGv/JlpPoWB365ldkppUlnizhMxJgojp907ZF1PgLTuW207kgzZcSCXyquL9qJkMsY+MRtaZ5yQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -743,8 +665,8 @@ packages: dev: true optional: true - /esbuild-windows-arm64/0.14.49: - resolution: {integrity: sha512-v+HYNAXzuANrCbbLFJ5nmO3m5y2PGZWLe3uloAkLt87aXiO2mZr3BTmacZdjwNkNEHuH3bNtN8cak+mzVjVPfA==} + /esbuild-windows-arm64/0.14.50: + resolution: {integrity: sha512-EMS4lQnsIe12ZyAinOINx7eq2mjpDdhGZZWDwPZE/yUTN9cnc2Ze/xUTYIAyaJqrqQda3LnDpADKpvLvol6ENQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -752,32 +674,32 @@ packages: dev: true optional: true - /esbuild/0.14.49: - resolution: {integrity: sha512-/TlVHhOaq7Yz8N1OJrjqM3Auzo5wjvHFLk+T8pIue+fhnhIMpfAzsG6PLVMbFveVxqD2WOp3QHei+52IMUNmCw==} + /esbuild/0.14.50: + resolution: {integrity: sha512-SbC3k35Ih2IC6trhbMYW7hYeGdjPKf9atTKwBUHqMCYFZZ9z8zhuvfnZihsnJypl74FjiAKjBRqFkBkAd0rS/w==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - esbuild-android-64: 0.14.49 - esbuild-android-arm64: 0.14.49 - esbuild-darwin-64: 0.14.49 - esbuild-darwin-arm64: 0.14.49 - esbuild-freebsd-64: 0.14.49 - esbuild-freebsd-arm64: 0.14.49 - esbuild-linux-32: 0.14.49 - esbuild-linux-64: 0.14.49 - esbuild-linux-arm: 0.14.49 - esbuild-linux-arm64: 0.14.49 - esbuild-linux-mips64le: 0.14.49 - esbuild-linux-ppc64le: 0.14.49 - esbuild-linux-riscv64: 0.14.49 - esbuild-linux-s390x: 0.14.49 - esbuild-netbsd-64: 0.14.49 - esbuild-openbsd-64: 0.14.49 - esbuild-sunos-64: 0.14.49 - esbuild-windows-32: 0.14.49 - esbuild-windows-64: 0.14.49 - esbuild-windows-arm64: 0.14.49 + esbuild-android-64: 0.14.50 + esbuild-android-arm64: 0.14.50 + esbuild-darwin-64: 0.14.50 + esbuild-darwin-arm64: 0.14.50 + esbuild-freebsd-64: 0.14.50 + esbuild-freebsd-arm64: 0.14.50 + esbuild-linux-32: 0.14.50 + esbuild-linux-64: 0.14.50 + esbuild-linux-arm: 0.14.50 + esbuild-linux-arm64: 0.14.50 + esbuild-linux-mips64le: 0.14.50 + esbuild-linux-ppc64le: 0.14.50 + esbuild-linux-riscv64: 0.14.50 + esbuild-linux-s390x: 0.14.50 + esbuild-netbsd-64: 0.14.50 + esbuild-openbsd-64: 0.14.50 + esbuild-sunos-64: 0.14.50 + esbuild-windows-32: 0.14.50 + esbuild-windows-64: 0.14.50 + esbuild-windows-arm64: 0.14.50 dev: true /escalade/3.1.1: @@ -820,11 +742,6 @@ packages: to-regex-range: 5.0.1 dev: true - /form-data-encoder/2.0.1: - resolution: {integrity: sha512-Oy+P9w5mnO4TWXVgUiQvggNKPI9/ummcSt5usuIV6HkaLKigwzPpoenhEqmGmx3zHqm6ZLJ+CR/99N8JLinaEw==} - engines: {node: '>= 14.17'} - dev: true - /fraction.js/4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true @@ -867,18 +784,6 @@ packages: wide-align: 1.1.5 dev: true - /get-stream/5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - dependencies: - pump: 3.0.0 - dev: true - - /get-stream/6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - dev: true - /glob-parent/5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -910,25 +815,6 @@ packages: /globrex/0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} - /got/12.2.0: - resolution: {integrity: sha512-A81ll5Z8wzeCmSdIlWVMDWFKDo82v2nmOaMZDQNHKGInNqDBcle+CSb6BBiZcn/Aiefz/kSpo520WBKi9QAO/A==} - engines: {node: '>=14.16'} - dependencies: - '@sindresorhus/is': 5.3.0 - '@szmarczak/http-timer': 5.0.1 - '@types/cacheable-request': 6.0.2 - '@types/responselike': 1.0.0 - cacheable-lookup: 6.0.4 - cacheable-request: 7.0.2 - decompress-response: 6.0.0 - form-data-encoder: 2.0.1 - get-stream: 6.0.1 - http2-wrapper: 2.1.11 - lowercase-keys: 3.0.0 - p-cancelable: 3.0.0 - responselike: 2.0.1 - dev: true - /graceful-fs/4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} dev: true @@ -944,18 +830,6 @@ packages: function-bind: 1.1.1 dev: true - /http-cache-semantics/4.1.0: - resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} - dev: true - - /http2-wrapper/2.1.11: - resolution: {integrity: sha512-aNAk5JzLturWEUiuhAN73Jcbq96R7rTitAoXV54FYMatvihnpD2+6PUgU4ce3D/m5VDbw+F5CsyKSF176ptitQ==} - engines: {node: '>=10.19.0'} - dependencies: - quick-lru: 5.1.1 - resolve-alpn: 1.2.1 - dev: true - /https-proxy-agent/5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -1021,17 +895,6 @@ packages: engines: {node: '>=0.12.0'} dev: true - /json-buffer/3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true - - /keyv/4.3.3: - resolution: {integrity: sha512-AcysI17RvakTh8ir03+a3zJr5r0ovnAH/XTXei/4HIv3bL2K/jzvgivLK9UuI/JbU1aJjM3NSAnVvVVd3n+4DQ==} - dependencies: - compress-brotli: 1.3.8 - json-buffer: 3.0.1 - dev: true - /kleur/4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} @@ -1042,16 +905,6 @@ packages: engines: {node: '>=10'} dev: true - /lowercase-keys/2.0.0: - resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} - engines: {node: '>=8'} - dev: true - - /lowercase-keys/3.0.0: - resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1092,16 +945,6 @@ packages: picomatch: 2.3.1 dev: true - /mimic-response/1.0.1: - resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} - engines: {node: '>=4'} - dev: true - - /mimic-response/3.1.0: - resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} - engines: {node: '>=10'} - dev: true - /min-indent/1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -1203,11 +1046,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /normalize-url/6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - dev: true - /npmlog/5.0.1: resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} dependencies: @@ -1233,11 +1071,6 @@ packages: wrappy: 1.0.2 dev: true - /p-cancelable/3.0.0: - resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} - engines: {node: '>=12.20'} - dev: true - /path-is-absolute/1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -1347,11 +1180,14 @@ packages: hasBin: true dev: true - /pump/3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} + /qr-code-styling/1.6.0-rc.1: + resolution: {integrity: sha512-ModRIiW6oUnsP18QzrRYZSc/CFKFKIdj7pUs57AEVH20ajlglRpN3HukjHk0UbNMTlKGuaYl7Gt6/O5Gg2NU2Q==} dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 + qrcode-generator: 1.4.4 + dev: true + + /qrcode-generator/1.4.4: + resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==} dev: true /queue-microtask/1.2.3: @@ -1390,10 +1226,6 @@ packages: engines: {node: '>=8'} dev: true - /resolve-alpn/1.2.1: - resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} - dev: true - /resolve-from/5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1408,12 +1240,6 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true - /responselike/2.0.1: - resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} - dependencies: - lowercase-keys: 2.0.0 - dev: true - /reusify/1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -1439,8 +1265,8 @@ packages: estree-walker: 0.6.1 dev: true - /rollup/2.77.0: - resolution: {integrity: sha512-vL8xjY4yOQEw79DvyXLijhnhh+R/O9zpF/LEgkCebZFtb6ELeN9H3/2T0r8+mp+fFTBHZ5qGpOpW2ela2zRt3g==} + /rollup/2.77.1: + resolution: {integrity: sha512-GhutNJrvTYD6s1moo+kyq7lD9DeR5HDyXo4bDFlDSkepC9kVKY+KK/NSZFzCmeXeia3kEzVuToQmHRdugyZHxw==} engines: {node: '>=10.0.0'} hasBin: true optionalDependencies: @@ -1489,8 +1315,8 @@ packages: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: true - /set-cookie-parser/2.5.0: - resolution: {integrity: sha512-cHMAtSXilfyBePduZEBVPTCftTQWz6ehWJD5YNUg4mqvRosrrjKbo4WS8JkB0/RxonMoohHm7cOGH60mDkRQ9w==} + /set-cookie-parser/2.5.1: + resolution: {integrity: sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==} dev: true /signal-exit/3.0.7: @@ -1709,8 +1535,8 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true - /vite/3.0.2: - resolution: {integrity: sha512-TAqydxW/w0U5AoL5AsD9DApTvGb2iNbGs3sN4u2VdT1GFkQVUfgUldt+t08TZgi23uIauh1TUOQJALduo9GXqw==} + /vite/3.0.3: + resolution: {integrity: sha512-sDIpIcl3mv1NUaSzZwiXGEy1ZoWwwC2vkxUHY6yiDacR6zf//ZFuBJrozO62gedpE43pmxnLATNR5IYUdAEkMQ==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -1728,10 +1554,10 @@ packages: terser: optional: true dependencies: - esbuild: 0.14.49 + esbuild: 0.14.50 postcss: 8.4.14 resolve: 1.22.1 - rollup: 2.77.0 + rollup: 2.77.1 optionalDependencies: fsevents: 2.3.2 dev: true diff --git a/src/components/404.svelte b/src/components/404.svelte new file mode 100644 index 00000000..503316bb --- /dev/null +++ b/src/components/404.svelte @@ -0,0 +1,16 @@ +<script> + import { Icon } from '$comp'; +</script> + +<div class="flex justify-center items-center mt-20"> + <div class="space-y-5 text-center"> + <a href="/"> + <Icon icon="logo" /> + </a> + + <Icon icon="question" style="mx-auto w-full" /> + + <h1 class="font-bold text-5xl">404</h1> + <p>Page not found.</p> + </div> +</div> diff --git a/src/components/About.svelte b/src/components/About.svelte new file mode 100644 index 00000000..3731bf9b --- /dev/null +++ b/src/components/About.svelte @@ -0,0 +1,73 @@ +<script> + import { Image, Icon } from '$comp'; + + const aboutAdam = [ + 'Running Bitcoin since 2011', + '20+ years web development experience in government and industry', + 'Early Bitcoin Vancouver meetup organizer', + 'Founder of The Bitcoin Co-op and early supporter of Decentral Vancouver', + 'Yogi, permaculturist, anarchist, decentralist', + 'Husband, father, brother, son' + ]; +</script> + +<h3 class="text-5xl font-medium mb-10 text-center">About</h3> +<div + class="md:px-10 lg:px-0 block lg:flex justify-center text-secondary text-xl lg:space-x-20 mx-auto max-w-7xl relative" +> + <p class="w-full lg:w-5/12"> + <strong>CoinOS is</strong> an <strong>open source</strong> bitcoin web wallet, point of sale, + ecommerce marketplace and exchange platform. Development started in Vancouver in September 2012 + as a way to provide local merchants with a convenient way to accept bitcoin payments. + <br /><br /> + The feature set has grown over the years to accomodate a wide variety of use cases with the goal + of making it as easy as possible for anyone to get started using Bitcoin quickly, cheaply, and securely. + <br /><br /> + </p> + <p class="w-full lg:w-5/12"> + Anyone can download the source code and host their own instance of the platform backed by their + own bitcoin, liquid or lightning nodes, or reach out and we'll gladly set this up for you. + <br /><br /> + Intra-account transfers between custodial wallets of different users on our server are completely + free, instant, and unlimited. + </p> + <Image image="swoosh" style="w-32 absolute -bottom-20 lg:bottom-0 right-0 md:right-10" /> +</div> + +<div class="px-5 block lg:flex justify-center lg:space-x-32 items-center mt-40 lg:mt-20"> + <div class="space-y-5 mb-5 lg:mb-0"> + <h3 class="text-5xl font-medium mb-10 text-center lg:text-right">The Founder</h3> + + <Image image="adam" style="rounded-full mx-auto lg:mx-0 lg:ml-auto w-40" /> + + <div class="space-y-2"> + <span class="text-2xl text-center font-semibold block lg:text-right">Adam Soltys</span> + <span class="text-xl text-center text-secondary block lg:text-right" + >Chief Technical Officer</span + > + </div> + + <div class="flex justify-center lg:justify-end items-center space-x-5"> + <a + href="https://twitter.com/adamsoltys" + target="_blank" + rel="noreferrer" + class="rounded-full p-2 bg-[#1D9BF0]"><Icon icon="twitter" style="w-6" /></a + > + <a + href="https://github.com/asoltys" + target="_blank" + rel="noreferrer" + class="rounded-full p-2 bg-gray-800"><Icon icon="github" style="w-6" /></a + > + </div> + </div> + + <ul + class="w-full mx-auto md:w-[700px] lg:w-[500px] xl:w-auto text-secondary px-1 md:px-10 text-xl list-disc space-y-5" + > + {#each aboutAdam as about} + <li>{about}</li> + {/each} + </ul> +</div> diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte index 249e2826..242e6924 100644 --- a/src/components/AppHeader.svelte +++ b/src/components/AppHeader.svelte @@ -1,32 +1,88 @@ <script> // we will want to dynamically use the users avatar on this screen if they have uploaded one + import { user } from '$lib/store'; import { goto } from '$app/navigation'; import { Icon, LocaleSelector } from '$comp'; - import { locale, locales } from 'svelte-i18n'; + export let avatarPosition = 'left-[calc(50vw-48px)]'; + + const menuButtons = [ + { title: 'Customers', icon: 'customers', goto: 'customers' }, + { title: 'Dashboard', icon: 'dash', goto: 'dashboard' }, + { title: 'Settings', icon: 'settings', goto: 'settings' }, + { title: 'Support', icon: 'support', goto: 'support' }, + { title: 'Sign out', icon: 'logout', goto: 'logout' } + ]; + + let showMenu = false; + + const handleMenuItemClick = (item) => { + if (item === 'logout') { + goto('/logout'); + } else { + goto(`/${$user.username + '/' + item}`); + } + }; </script> -<header class="bg-gradient-to-r from-primary to-gradient h-[20vh] w-full relative"> +<header class="bg-gradient-to-r from-primary to-gradient h-[175px] w-full relative"> <nav class="flex justify-end items-center space-x-4 p-5"> <LocaleSelector /> - - <button class="bg-white p-2 rounded-full w-12 h-12" on:click={() => goto('/receive')} - ><Icon icon="numpad" style="mx-auto" /></button - > - <button class="bg-white p-2 rounded-full w-12 h-12" - ><Icon icon="clock" style="mx-auto" /></button - > - <button class="bg-white p-2 rounded-full w-12 h-12" - ><Icon icon="menu" style="mx-auto" /></button - > - <button class="bg-white p-2 rounded-full w-12 h-12" - ><Icon icon="profile" style="mx-auto" /></button - > + {#if $user} + <a href={`/${$user.username}/receive`}> + <button class="bg-white p-2 rounded-full w-12 h-12" + ><Icon icon="numpad" style="mx-auto" /> + </button> + </a> + <a href={`/${$user.username}/transactions`}> + <button class="bg-white p-2 rounded-full w-12 h-12" + ><Icon icon="clock" style="mx-auto" /> + </button> + </a> + <a href={`/${$user.username}`}> + <button class="bg-white p-2 rounded-full w-12 h-12" + ><Icon icon="profile" style="mx-auto" /> + </button> + </a> + <div class="relative"> + <button class="bg-white p-2 rounded-full w-12 h-12" on:click={() => (showMenu = !showMenu)} + ><Icon icon="menu" style="mx-auto" /> + </button> + <div + class="{showMenu + ? 'block' + : 'hidden'} absolute top-14 right-0 bg-white rounded-3xl p-8 shadow-xl z" + > + <ul class="space-y-5 w-32"> + {#each menuButtons as button} + <li> + <button + class="flex justify-center items-center font-semibold text-sm" + on:click={() => handleMenuItemClick(button.goto)} + ><Icon icon={button.icon} style="mr-1" /> {button.title} + </button> + </li> + {/each} + </ul> + </div> + </div> + {:else} + <a href="/login"> + <button class="bg-white px-5 py-2 rounded-xl font-semibold text-sm">Sign in </button> + </a> + {/if} +>>>>>>> master </nav> <div - class="absolute top-[calc(20vh-48px)] left-[calc(50vw-48px)] rounded-full border-4 border-white p-4 bg-gradient-to-r from-primary to-gradient w-24 h-24" + class="absolute top-[calc(175px-48px)] {avatarPosition} rounded-full border-4 border-white p-4 bg-gradient-to-r from-primary to-gradient w-24 h-24" > <Icon icon="logo-symbol-white" style="mx-auto" /> </div> </header> + +<style> + .z { + z-index: 100; + } +</style> diff --git a/src/components/Footer.svelte b/src/components/Footer.svelte new file mode 100644 index 00000000..0c682179 --- /dev/null +++ b/src/components/Footer.svelte @@ -0,0 +1,39 @@ +<script> + import { Icon } from '$comp'; + + const followLinks = [ + { url: 'https://twitter.com/coinoswallet', title: 'Twitter' }, + { url: 'https://github.com/coinos', title: 'GitHub' }, + { url: 'https://t.me/coinos', title: 'Telegram' } + ]; + + const companyLinks = [ + { url: 'mailto:[email protected]', title: 'Support' }, + { url: '/community', title: 'Community' }, + { url: '/jobs', title: 'Jobs' } + ]; +</script> + +<footer class="space-y-10 md:space-y-0 text-xl block md:flex justify-around items-center mb-20"> + <Icon icon="logo" style="w-48 mx-auto md:mx-0" /> + + <div class="text-center md:text-left"> + <p class="font-semibold">Follow Us</p> + <ul class="mt-5 text-secondary space-y-3 font-medium"> + {#each followLinks as link} + <li><a href={link.url} target="_blank" rel="noreferrer">{link.title}</a></li> + {/each} + </ul> + </div> + + <div class="text-center md:text-left"> + <p class="font-semibold">Company</p> + <ul class="mt-5 text-secondary space-y-3"> + {#each companyLinks as link} + <li><a href={link.url}>{link.title}</a></li> + {/each} + </ul> + </div> + + <Icon icon="shield" style="mx-auto md:mx-0" /> +</footer> diff --git a/src/components/LandingHeader.svelte b/src/components/LandingHeader.svelte index ca9d431e..352b200c 100644 --- a/src/components/LandingHeader.svelte +++ b/src/components/LandingHeader.svelte @@ -2,34 +2,112 @@ import { goto } from '$app/navigation'; import { scroll } from '$lib/utils'; import { Icon, LocaleSelector } from '$comp'; - - import { _, locale, locales } from 'svelte-i18n'; + import { user } from '$lib/store'; + import { _ } from 'svelte-i18n'; export let howItWorks; export let faq; + export let about; + + let showMobileMenu = false; + const mobileMenuButtonClick = (section) => { + showMobileMenu = false; + scroll(section); + }; </script> -<header class="w-full lg:w-5/6 pt-10 mx-auto"> +<header + class="w-full lg:w-5/6 py-5 xl:pb-0 xl:pt-10 mx-auto fixed md:sticky z-10 top-0 bg-white/90" +> <nav class="block xl:flex flex-wrap justify-between items-center"> - <a href="/"> + <a href="/" class="hidden md:block"> <Icon icon="logo" style="mx-auto mb-5 xl:mb-0" /> </a> + <Icon icon="logo" style="block md:hidden ml-5 w-48" /> <LocaleSelector /> + <!-- desktop nav --> <div class="hidden space-x-10 md:flex flex-wrap justify-center items-center font-bold"> <button on:click={() => scroll(howItWorks)}>{$_('howItWorks')}</button> <button on:click={() => scroll(faq)}>{$_('FAQ')}</button> - <button>{$_('about')}</button> - <button class="border rounded-full px-6 py-2 font-bold" on:click={() => goto('/register')} - >{$_('startInSeconds')}</button - > - <button - class="bg-black text-white border rounded-full px-6 py-2 font-bold" - on:click={() => goto('/login')} - > - {$_('signIn')} - </button> + <button on:click={() => scroll(about)}>{$_('about')}</button> + {#if !$user} + <button class="border rounded-full px-6 py-2 font-bold" on:click={() => goto('/register')} + >{$_('startInSeconds')} + </button> + <button + class="bg-black text-white border rounded-full px-6 py-2 font-bold" + on:click={() => goto('/login')} + > + {$_('signIn')} + </button> + {:else} + <button + class="border rounded-full px-6 py-2 font-bold" + on:click={() => goto(`/${$user.username}/dashboard`)} + >Account + </button> + <button + class="bg-black text-white border rounded-full px-6 py-2 font-bold" + on:click={() => goto('/logout')} + > + Sign out + </button> + {/if} + </div> + + <!-- mobile nav --> + <button + class="block md:hidden absolute top-[34.5px] right-10 z" + on:click={() => (showMobileMenu = !showMobileMenu)} + ><Icon icon={!showMobileMenu ? 'menu' : 'close'} /> + </button> + + <div + class="block md:hidden absolute top-0 {showMobileMenu + ? 'right-0' + : 'right-[-100%]'} transition-all ease-in-out duration-300 h-[100vh] w-full bg-white" + > + <div class="space-y-8 mt-24 ml-10 font-bold text-xl"> + <button on:click={() => mobileMenuButtonClick(howItWorks)} class="block" + >{$_('howItWorks')}</button + > + <button on:click={() => mobileMenuButtonClick(faq)} class="block">{$_('FAQ')}</button> + <button on:click={() => mobileMenuButtonClick(about)} class="block">{$_('about')}</button> + {#if !$user} + <button + class="border rounded-full px-6 py-2 font-bold block" + on:click={() => goto('/register')} + >{$_('startInSeconds')} + </button> + <button + class="bg-black text-white border rounded-full px-6 py-2 font-bold block" + on:click={() => goto('/login')} + > + {$_('startInSeconds')} + </button> + {:else} + <button + class="border rounded-full px-6 py-2 font-bold block" + on:click={() => goto(`/${$user.username}/dashboard`)} + >Account + </button> + <button + class="bg-black text-white border rounded-full px-6 py-2 font-bold block" + on:click={() => goto('/logout')} + > + Sign out + </button> + {/if} + </div> +>>>>>>> master </div> </nav> </header> + +<style> + .z { + z-index: 100; + } +</style> diff --git a/src/components/LandingHero.svelte b/src/components/LandingHero.svelte index 2c2e1ab5..93bea8c2 100644 --- a/src/components/LandingHero.svelte +++ b/src/components/LandingHero.svelte @@ -1,9 +1,10 @@ <script> import { goto } from '$app/navigation'; import { Icon, Image } from '$comp'; + import { user } from '$lib/store'; </script> -<div> +<div class="mt-[96.88px] md:mt-0"> <div class="space-y-10"> <h1 class="text-5xl md:text-6xl lg:text-7xl 2xl:text-8xl font-medium text-center w-full md:w-3/4 lg:w-1/2 max-w-7xl mx-auto leading-tight" @@ -19,14 +20,16 @@ Grow your customer base and revenue by accepting Bitcoin with an easy-to-use and managed Point of Sale. No software or hardware required. </h2> - <div class="flex justify-center"> - <button - class="bg-black text-white border rounded-full px-6 py-2 font-bold" - on:click={() => goto('/register')} - > - Start in Seconds - </button> - </div> + {#if !$user} + <div class="flex justify-center"> + <button + class="bg-black text-white border rounded-full px-6 py-2 font-bold" + on:click={() => goto('/register')} + > + Start in Seconds + </button> + </div> + {/if} </div> <!-- hero image --> diff --git a/src/components/LoadingSplash.svelte b/src/components/LoadingSplash.svelte new file mode 100644 index 00000000..2ab4c739 --- /dev/null +++ b/src/components/LoadingSplash.svelte @@ -0,0 +1,7 @@ +<script> + import { Icon } from '$comp'; +</script> + +<div class="flex justify-center items-center"> + <Icon icon="logo" style="mt-[calc(50vh-72px)]" /> +</div> diff --git a/src/components/RegisterOrLogin.svelte b/src/components/RegisterOrLogin.svelte index 7cc41709..157c2821 100644 --- a/src/components/RegisterOrLogin.svelte +++ b/src/components/RegisterOrLogin.svelte @@ -1,13 +1,15 @@ <script> + import { toast } from '@zerodevx/svelte-toast'; import { Icon } from '$comp'; import { post } from '$lib/utils'; - import { token } from '$lib/store'; - import { connect } from '$lib/socket'; + import { user, token } from '$lib/store'; + import { auth } from '$lib/socket'; import { goto } from '$app/navigation'; export let page; - let username, password; + let username = 'bob', + password = 'pw'; let revealPassword = false; @@ -16,20 +18,27 @@ 'Sign in': '/login' }[page]; - let submit = () => - post(url, { username, password }).then((r) => - r.json().then((r) => { - $token = r.token; - connect(); - goto('/receive'); - }) - ); + let submit = async () => { + try { + let r = await post(url, { username, password }); + if (!r.token) r = await post('/login', { username, password }); + $token = r.token; + auth(); + } catch (e) { + if (!e.message) e.message = 'Login failed'; + toast.push(e.message); + } + }; + + $: if ($user) goto(`/${username}/receive`); </script> <div class="pt-10"> - <a href="/"> - <Icon icon="logo" style="mx-auto mb-10" /> - </a> + <div class="w-[243px] mx-auto mb-10"> + <a href="/"> + <Icon icon="logo" /> + </a> + </div> <div class="flex justify-center items-center"> <div class="shadow-xl rounded-3xl p-10 pb-12 space-y-5 w-full mx-5 md:mx-0 md:w-[400px]"> @@ -38,11 +47,13 @@ <form class="space-y-5" on:submit|preventDefault={submit}> <div> <label for="username" class="font-semibold">Username</label> + <!-- svelte-ignore a11y-autofocus --> <input type="text" required class="bg-primary p-4 rounded-2xl w-full" bind:value={username} + autofocus /> </div> @@ -78,7 +89,9 @@ </p> {:else} <div class="flex justify-end items-center"> - <a href="" class="underline underline-offset-4 text-black text-sm">Forgot Password?</a> + <a href="/forgot" class="underline underline-offset-4 text-black text-sm" + >Forgot Password?</a + > </div> {/if} diff --git a/src/components/index.js b/src/components/index.js index f0e86183..1d5b369d 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -8,3 +8,7 @@ export { default as Image } from './Image.svelte'; export { default as RegisterOrLogin } from './RegisterOrLogin.svelte'; export { default as AppHeader } from './AppHeader.svelte'; export { default as LocaleSelector } from './LocaleSelector.svelte'; +export { default as LoadingSplash } from './LoadingSplash.svelte'; +export { default as PageNotFound } from './404.svelte'; +export { default as About } from './About.svelte'; +export { default as Footer } from './Footer.svelte'; diff --git a/src/hooks/index.js b/src/hooks/index.js new file mode 100644 index 00000000..2284d88d --- /dev/null +++ b/src/hooks/index.js @@ -0,0 +1,25 @@ +import cookie from 'cookie'; +import { get } from '$lib/utils'; +import { protectedRoutes } from '$lib/utils'; + +export async function handle({ event, resolve }) { + let { + params, + request: { headers } + } = event; + + const cookies = cookie.parse(headers.get('cookie') || ''); + let { token } = cookies; + event.locals = { token }; + + if (protectedRoutes.find((p) => event.url.pathname.match(p))) { + let user = await get('/me', { accept: 'application/json', authorization: `Bearer ${token}` }); + if (user.username !== params.username) throw new Error('user not found'); + } + + return resolve(event); +} + +export const getSession = ({ locals: { token } }) => { + return { token }; +}; diff --git a/src/lib/socket.js b/src/lib/socket.js index efc2651b..66091d76 100644 --- a/src/lib/socket.js +++ b/src/lib/socket.js @@ -1,23 +1,31 @@ -import { get } from "svelte/store"; -import { rate, user, token, ws } from '$lib/store'; +import { get } from 'svelte/store'; +import { rate, user, token, invoices, preferredCurrency } from '$lib/store'; let interval, socket; +export const auth = () => { + let t = get(token); + if (t) send('login', t); +}; + +export const send = (type, data) => { + socket?.readyState === 1 && socket.send(JSON.stringify({ type, data })); +}; + export const messages = (data) => ({ rate() { rate.set(data); }, payment() { - console.log(data); + invoices.set({ ...get(invoices), [data.invoice.uuid]: data.invoice }); }, - connected() { - socket.send(JSON.stringify({ type: 'login', data: { token: get(token) } })); - }, + connected: auth, login() { user.set(data); + preferredCurrency.set(data.currency); } }); @@ -29,18 +37,18 @@ let currentReconnectDelay = initialReconnectDelay; export function connect() { clearInterval(interval); - if (socket) socket.close(); + if (socket) return auth(); + socket = new WebSocket('ws://localhost:3119/ws'); socket.addEventListener('open', onWebsocketOpen); socket.addEventListener('close', onWebsocketClose); socket.addEventListener('message', onWebsocketMessage); - interval = setInterval( - () => socket.readyState === 1 && socket.send(JSON.stringify({ type: 'heartbeat' })), - 5000 - ); + interval = setInterval(() => send('heartbeat'), 5000); +} - ws.set(socket); +export function close() { + if (socket) socket.close(); } function onWebsocketMessage(msg) { @@ -53,7 +61,7 @@ function onWebsocketOpen() { } function onWebsocketClose() { - ws.set(null); + socket = null; setTimeout(() => { reconnectToWebsocket(); }, currentReconnectDelay + Math.floor(Math.random() * 3000)); diff --git a/src/lib/store.js b/src/lib/store.js index 3963f55a..b20fd10f 100644 --- a/src/lib/store.js +++ b/src/lib/store.js @@ -1,6 +1,22 @@ -import { writable } from 'svelte/store'; +import { get, writable } from 'svelte/store'; +import { browser } from '$app/env'; + +const persisted = (k, i) => { + let s = writable( + browser && sessionStorage.getItem(k) && sessionStorage.getItem(k) !== 'undefined' + ? JSON.parse(sessionStorage.getItem(k)) + : i + ); + + s.subscribe((v) => browser && sessionStorage.setItem(k, JSON.stringify(v))); + + return s; +}; export const rate = writable(); export const user = writable(); -export const token = writable(); -export const ws = writable(); +export const invoiceAmount = writable(); +export const invoiceAmountFiat = writable(); +export const token = persisted('token'); +export const invoices = writable({}); +export const preferredCurrency = writable(); diff --git a/src/lib/utils.js b/src/lib/utils.js index 595d2dd8..669b2b5e 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,10 +1,53 @@ +import { browser } from '$app/env'; + export function scroll(section) { section.scrollIntoView({ behavior: 'smooth' }); } +const base = browser ? '' : import.meta.env.VITE_COINOS_URL; + +export const get = (url, headers = { accept: 'application/json' }) => + fetch(base + url, { headers }) + .then((r) => r.text()) + .then((body) => { + try { + return JSON.parse(body); + } catch (e) { + throw new Error(body); + } + }); + export const post = (url, body) => - fetch(url, { + fetch(base + url, { method: 'POST', body: JSON.stringify(body), - headers: { 'accept': 'application/json', 'content-type': 'application/json' } - }); + headers: { accept: 'application/json', 'content-type': 'application/json' } + }) + .then((r) => r.text()) + .then((body) => { + try { + body = JSON.parse(body); + } catch (e) { + throw new Error(body); + } + + if (body instanceof Error) throw body; + if (body.name === 'Error') throw new Error(body.message); + + return body; + }); + +export const copy = (text) => { + navigator.clipboard.writeText(text); +}; + +export function reverseFormat(val, locale) { + let parts = new Intl.NumberFormat(locale).formatToParts(1111.1); + let group = parts.find((part) => part.type === 'group').value; + let decimal = parts.find((part) => part.type === 'decimal').value; + let reversedVal = val.replace(new RegExp('\\' + group, 'g'), ''); + reversedVal = reversedVal.replace(new RegExp('\\' + decimal, 'g'), '.'); + return Number.isNaN(reversedVal) ? 0 : +reversedVal; +} + +export let protectedRoutes = [/receive/, /customers/, /dashboard/, /settings/, /transactions/]; diff --git a/src/routes/[username]/customers.svelte b/src/routes/[username]/customers.svelte new file mode 100644 index 00000000..1935b156 --- /dev/null +++ b/src/routes/[username]/customers.svelte @@ -0,0 +1,5 @@ +<script> + import { AppHeader } from '$comp'; +</script> + +<AppHeader /> diff --git a/src/routes/[username]/dashboard.svelte b/src/routes/[username]/dashboard.svelte new file mode 100644 index 00000000..1935b156 --- /dev/null +++ b/src/routes/[username]/dashboard.svelte @@ -0,0 +1,5 @@ +<script> + import { AppHeader } from '$comp'; +</script> + +<AppHeader /> diff --git a/src/routes/[username]/index.js b/src/routes/[username]/index.js new file mode 100644 index 00000000..5de1316f --- /dev/null +++ b/src/routes/[username]/index.js @@ -0,0 +1,14 @@ +import { get } from '$lib/utils'; + +export async function GET({ params }) { + let { username } = params; + let body; + + try { + body = await get(`/users/${username}`); + } catch (e) { + body = {}; + } + + return { body }; +} diff --git a/src/routes/[username]/index.svelte b/src/routes/[username]/index.svelte new file mode 100644 index 00000000..6002efdc --- /dev/null +++ b/src/routes/[username]/index.svelte @@ -0,0 +1,95 @@ +<script> + import { toast } from '@zerodevx/svelte-toast'; + import { browser } from '$app/env'; + import { onMount } from 'svelte'; + import { AppHeader, Icon, PageNotFound } from '$comp'; + import { user } from '$lib/store'; + import { copy } from '$lib/utils'; + export let username; + + let lightningAddress = `${username}@coinos.io`; + + onMount(async () => { + if (browser) { + let { default: QRCodeStyling } = await import('qr-code-styling'); + const qrCode = new QRCodeStyling({ + width: window.screen.width < 640 ? 250 : 300, + type: 'svg', + data: 'add static qr here', + image: '/images/invoice.svg', + backgroundOptions: { + color: 'rgba(0, 0, 0, 0)' + }, + dotsOptions: { + type: 'rounded' + }, + imageOptions: { + hideBackgroundDots: false + } + }); + + qrCode.append(document.getElementById('qr')); + } + }); + + let followed = false; + + let qrSkeleton = true; + setTimeout(() => (qrSkeleton = false), 100); + + const handleCopy = () => { + copy(lightningAddress); + toast.push('Copied!', { + theme: { + '--toastBarBackground': '#2F855A' + } + }); + }; +</script> + +{#if username} + <AppHeader avatarPosition="left-[calc(15vw-48px)]" /> + + <div class="flex xl:block justify-center items-center my-20 px-3"> + <div class="space-y-10 xl:space-y-0"> + <div class="xl:ml-[calc(15vw-48px)] space-y-2 w-full md:w-72"> + <h1 class="text-3xl font-bold">{username}</h1> + + <p class="text-secondary">Address goes here</p> + + <button + class="border rounded-full py-2 w-28 font-semibold flex justify-center items-center text-sm" + on:click={() => (followed = !followed)} + ><Icon icon={followed ? 'minus' : 'plus'} style="mr-1" /> + {followed ? 'Remove' : 'Follow'} + </button> + </div> + + <div class="space-y-5 static xl:absolute top-[255px] left-[calc(50vw-150px)]"> + <div + id="qr" + class="border border-lightgrey rounded-3xl flex md:p-5 justify-center items-center relative" + > + <div + class="z-100 h-[300px] md:h-[342px] w-[250px] md:w-[342px] animate-pulse absolute top-0 left-0 bg-gray-400 rounded-3xl {qrSkeleton + ? 'block' + : 'hidden'}" + /> + </div> + + <p class="text-secondary text-center font-semibold">Scan to pay with Bitcoin</p> + + <div + class="bg-primary font-semibold rounded-xl text-sm py-2 px-3 flex flex-wrap justify-center items-center" + > + <span>{lightningAddress}</span> + <button on:click={handleCopy}> + <Icon icon="copy" style="ml-2" /> + </button> + </div> + </div> + </div> + </div> +{:else} + <PageNotFound /> +{/if} diff --git a/src/routes/[username]/receive.svelte b/src/routes/[username]/receive.svelte new file mode 100644 index 00000000..50fddbed --- /dev/null +++ b/src/routes/[username]/receive.svelte @@ -0,0 +1,187 @@ +<script> + // in the future we will want to allow users to set their fiat currency in the settings + import { AppHeader, Icon } from '$comp'; + import { goto } from '$app/navigation'; + import { invoiceAmount, invoiceAmountFiat, rate, user } from '$lib/store'; + import { post } from '$lib/utils'; + import { page } from '$app/stores'; + + let useFiat = true; + let amountFiat = 0; + let amountSats = 0; + let message = ''; + + $: amount = useFiat ? Math.round(amountFiat / ($rate / 100000000)) : amountSats; + + $: $invoiceAmount = parseInt(amountSats === 0 ? amountFiat / ($rate / 100000000) : amountSats); + $: $invoiceAmountFiat = parseInt( + amountFiat === 0 ? amountSats * ($rate / 100000000) : amountFiat + ); + $: amountSatsFormatted = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format( + amountSats + ); + + $: amountFiatConverted = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(amountSats * ($rate / 100000000)); + + $: amountSatsConverted = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format( + amountFiat / ($rate / 100000000) + ); + + const numPad = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0', '<']; + + const handleInput = (value) => { + if (message) { + message = ''; + } + if (useFiat) { + if (amountFiat === 0 && value !== '.' && value !== '<' && value !== '0') { + amountFiat = value; + } else if ((amountFiat === 0 || amountFiat === '0') && value === '0') { + return; + } else if (amountFiat === 0 && value === '.') { + amountFiat = '0.'; + } else if (amountFiat !== 0 && amountFiat.includes('.') && value === '.') { + return; + } else if (value === '<') { + if (amountFiat !== 0) { + amountFiat = amountFiat.slice(0, amountFiat.length - 1); + if (amountFiat.length === 0) { + amountFiat = 0; + amountSats = 0; + } + } + } else if (value !== '.' && value !== '<' && parseInt(amountFiat + value) > $rate) { + message = 'Please enter an amount less than 1 bitcoin.'; + } else if (amountFiat !== 0 && amountFiat.match(/\.../)) { + return; + } else { + amountFiat = amountFiat + value; + } + } else { + if (value === '.') { + return; + } else if (amountSats === 0 && value !== '<' && value !== '0') { + amountSats = value; + } else if (value === '<') { + if (amountSats !== 0) { + amountSats = amountSats.slice(0, amountSats.length - 1); + if (amountSats.length === 0) { + amountSats = 0; + amountFiat = 0; + } + } + } else if (value !== '<' && parseInt(amountSats + value) > 100000000) { + message = 'Please enter an amount less than 1 bitcoin.'; + } else { + amountSats = amountSats + value; + } + } + }; + + let submit = async () => { + let { id } = await post('/invoice', { amount, rate: $rate, username: $page.params.username }); + goto(`/invoice/${id}`); + }; +</script> + +{#if $user} + <AppHeader /> + + <div class="flex justify-center items-center mt-20 mb-3 px-3"> + {#if $rate} + <div class="space-y-5"> + <!-- amounts --> + <div class="text-center"> + <div class="text-5xl md:text-6xl font-semibold tracking-widest mb-1"> + {useFiat ? `$${amountFiat}` : amountSatsFormatted}<span + class="tracking-normal text-base font-normal">{useFiat ? 'USD' : 'SATS'}</span + > + </div> + <span class="text-secondary mr-1" + >{useFiat ? `${amountSatsConverted} SATS` : `${amountFiatConverted} USD`}</span + > + <button + on:click={() => { + if (useFiat) { + amountSats = (amountFiat / ($rate / 100000000)).toFixed(0).toString(); + } else { + amountFiat = + (amountSats * ($rate / 100000000)).toFixed(2) > 0.0 + ? (amountSats * ($rate / 100000000)).toFixed(2).toString() + : 0; + } + useFiat = !useFiat; + }}><Icon icon="swap" style="inline" /></button + > + </div> + + <!-- error message --> + {#if message} + <div + class="text-orange-500 text-sm text-center font-bold bg-primary p-2 rounded-xl flex justify-center items-center space-x-1" + > + <Icon icon="info" style="w-5" /> + <p>{message}</p> + </div> + {/if} + + <!-- numpad --> + <div class="grid grid-cols-3 gap-2 w-full md:w-[300px] mx-auto"> + {#each numPad as value} + {#if value === '<'} + <button + class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white flex justify-center items-center" + on:click={() => handleInput(value)} + > + <svg + class="w-5" + width="24" + height="24" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M15 19L8 12L15 5" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + /> + </svg> + </button> + {:else} + <button + class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white" + on:click={() => handleInput(value)}>{value}</button + > + {/if} + {/each} + </div> + + <!-- buttons --> + <div class="space-y-3 w-full md:w-[300px] mx-auto"> + <button + class="bg-black text-white rounded-xl w-full h-[48px] flex justify-center items-center font-semibold text-sm {$invoiceAmount === + 0 + ? 'opacity-50' + : 'opacity-100'}" + on:click={submit} + disabled={$invoiceAmount === 0}><Icon icon="qr" style="mr-2" /> Show QR</button + > + <button + class="bg-primary rounded-xl w-full h-[48px] flex justify-center items-center font-semibold text-sm" + disabled={true}><Icon icon="send" style="mr-2" /> Send Invoice</button + > + </div> + </div> + {:else} + <div + class="w-full md:w-[300px] h-[475px] md:h-[485px] animate-pulse bg-gray-400 rounded-xl" + /> + {/if} + </div> +{/if} diff --git a/src/routes/[username]/settings.svelte b/src/routes/[username]/settings.svelte new file mode 100644 index 00000000..1935b156 --- /dev/null +++ b/src/routes/[username]/settings.svelte @@ -0,0 +1,5 @@ +<script> + import { AppHeader } from '$comp'; +</script> + +<AppHeader /> diff --git a/src/routes/[username]/transactions.svelte b/src/routes/[username]/transactions.svelte new file mode 100644 index 00000000..1935b156 --- /dev/null +++ b/src/routes/[username]/transactions.svelte @@ -0,0 +1,5 @@ +<script> + import { AppHeader } from '$comp'; +</script> + +<AppHeader /> diff --git a/src/routes/__error.svelte b/src/routes/__error.svelte new file mode 100644 index 00000000..2d617f9d --- /dev/null +++ b/src/routes/__error.svelte @@ -0,0 +1,38 @@ +<script context="module"> + export function load({ error, status }) { + if (error.message.includes('not found')) { + error.message = 'Page not found'; + status = 404; + } + + return { + props: { + error, + status + } + }; + } +</script> + +<script> + import { Icon, PageNotFound } from '$comp'; + export let status, error; +</script> + +{#if status === 404} + <PageNotFound /> +{:else} + <div class="flex justify-center items-center mt-20"> + <div class="space-y-5 text-center"> + <a href="/"> + <Icon icon="logo" /> + </a> + + <Icon icon="error" style="mx-auto w-full" /> + + <h1 class="font-bold text-5xl">Error</h1> + <p>{error.message}</p> + <p>Please try again or contact support.</p> + </div> + </div> +{/if} diff --git a/src/routes/__layout.svelte b/src/routes/__layout.svelte index ed1ad188..9ec36c57 100644 --- a/src/routes/__layout.svelte +++ b/src/routes/__layout.svelte @@ -1,11 +1,47 @@ <script> + import { SvelteToast } from '@zerodevx/svelte-toast'; + import { toast } from '@zerodevx/svelte-toast'; import '../app.css'; + import '$lib/i18n'; import { onMount } from 'svelte'; import { connect } from '$lib/socket'; + import { user, token } from '$lib/store'; + import { page, session } from '$app/stores'; + import { browser } from '$app/env'; + import { goto } from '$app/navigation'; + import { LoadingSplash } from '$comp'; + import { protectedRoutes } from '$lib/utils'; - import '$lib/i18n'; + browser && ($token ||= $session.token); + let ready = false; - onMount(connect); + onMount(() => { + if (protectedRoutes.find((p) => $page.url.pathname.match(p))) { + if (!$token) { + goto('/login'); + toast.push('Please sign in to continue.'); + } + user.subscribe((u) => u?.username && (ready = true)); + } else { + ready = true; + } + + connect(); + }); </script> -<slot /> +{#if ready} + <SvelteToast options={{ reversed: true, intro: { y: 192 } }} /> + <slot /> +{:else} + <LoadingSplash /> +{/if} + +<style> + :root { + --toastContainerTop: auto; + --toastContainerRight: auto; + --toastContainerBottom: 8rem; + --toastContainerLeft: calc(50vw - 8rem); + } +</style> diff --git a/src/routes/community.svelte b/src/routes/community.svelte new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/routes/community.svelte diff --git a/src/routes/index.svelte b/src/routes/index.svelte index cda885a3..faa881bb 100644 --- a/src/routes/index.svelte +++ b/src/routes/index.svelte @@ -1,5 +1,14 @@ <script> - import { LandingHeader, LandingHero, LandingInfoCard, HowItWorksCard, FaqCard } from '$comp'; + import { + LandingHeader, + LandingHero, + LandingInfoCard, + HowItWorksCard, + FaqCard, + Image, + About, + Footer + } from '$comp'; let howItWorks; let faq; @@ -42,9 +51,9 @@ ]; </script> -<LandingHeader {howItWorks} {faq} /> +<LandingHeader {howItWorks} {faq} {about} /> -<main class="space-y-40 py-20 md:py-32 lg:py-40 px-5 md:px-0"> +<main class="space-y-40 py-20 md:py-32 lg:py-36 xl:py-40 px-5 md:px-0"> <!-- hero --> <LandingHero /> @@ -73,15 +82,18 @@ </LandingInfoCard> <!-- landing info 3 --> - <LandingInfoCard - image="customize" - title="Brand it your way!" - description="Customize your point of sale look and feel, or keep it on default - totally up to you." - /> + <div> + <LandingInfoCard + image="customize" + title="Brand it your way!" + description="Customize your point of sale look and feel, or keep it on default - totally up to you." + /> + <div bind:this={howItWorks} /> + </div> <!-- how it works --> - <div bind:this={howItWorks}> - <h3 class="text-5xl font-medium mb-20 text-center">How it works</h3> + <div> + <h3 class="text-5xl font-medium mb-10 text-center">How it works</h3> <div class="block lg:flex justify-center items-center space-y-10 lg:space-y-0 lg:space-x-10 text-center" > @@ -89,13 +101,24 @@ <HowItWorksCard image={step.image} step={step.step} description={step.description} /> {/each} </div> + <div bind:this={faq} /> </div> <!-- faq --> - <div class="space-y-10" bind:this={faq}> - <h3 class="text-5xl font-medium text-center">FAQ</h3> - {#each faqQuestions as question} - <FaqCard question={question.question} answer={question.answer} /> - {/each} + <div> + <div class="space-y-10"> + <h3 class="text-5xl font-medium text-center">FAQ</h3> + {#each faqQuestions as question} + <FaqCard question={question.question} answer={question.answer} /> + {/each} + </div> + <div bind:this={about} /> + </div> + + <!-- about --> + <div> + <About /> </div> </main> + +<Footer /> diff --git a/src/routes/invoice.js b/src/routes/invoice.js deleted file mode 100644 index 49bcfe70..00000000 --- a/src/routes/invoice.js +++ /dev/null @@ -1,20 +0,0 @@ -import got from 'got'; - -const username = 'bob', - network = 'lightning'; - -export async function GET({ params }) { - let text = await got - .post('http://localhost:3119/lightning/invoice', { - json: { amount: 100 } - }) - .text(); - - await got - .post('http://localhost:3119/invoice', { - json: { invoice: { text, network }, user: { username } } - }) - .json(); - - return { body: { text } }; -} diff --git a/src/routes/invoice.svelte b/src/routes/invoice.svelte deleted file mode 100644 index b8f9e54b..00000000 --- a/src/routes/invoice.svelte +++ /dev/null @@ -1,18 +0,0 @@ -<script> - import { rate, user } from '$lib/store'; - import { onMount } from 'svelte'; - - export let text; -</script> - -QR code - -{text} - -{$rate} - -{$user?.username} - -{#each $user.payments as p} - {p.amount}<br /> -{/each} diff --git a/src/routes/invoice/[id].js b/src/routes/invoice/[id].js new file mode 100644 index 00000000..a58d02a1 --- /dev/null +++ b/src/routes/invoice/[id].js @@ -0,0 +1,14 @@ +import { get } from '$lib/utils'; + +export async function GET({ params }) { + let { id } = params; + let { + amount, + rate, + status, + text, + user: { username } + } = await get(`/invoice?uuid=${id}`); + + return { body: { amount, id, rate, username, status, text } }; +} diff --git a/src/routes/invoice/[id].svelte b/src/routes/invoice/[id].svelte new file mode 100644 index 00000000..17c7de1f --- /dev/null +++ b/src/routes/invoice/[id].svelte @@ -0,0 +1,357 @@ +<script> + import { toast } from '@zerodevx/svelte-toast'; + import { get, post, reverseFormat } from '$lib/utils'; + import { browser } from '$app/env'; + import { invoices, user, preferredCurrency } from '$lib/store'; + import { Icon, Image } from '$comp'; + import { goto } from '$app/navigation'; + import { onMount } from 'svelte'; + import { copy } from '$lib/utils'; + + export let amount, id, rate, status, text, username; + + $invoices[id] = { amount, id, rate, status, text, username }; + + $: amountFiat = parseFloat(((amount * rate) / 100000000).toFixed(2)); + + let tipPercent = 0; + let qr; + + $: tipAmount = customTipAmount + ? customTipAmount < 0 + ? (customTipAmount = 0) + : customTipAmount + : (amountFiat / 100) * tipPercent; + + $: tipAmountFormatted = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(tipAmount); + + $: tipAmountSats = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format( + tipAmount / (rate / 100000000) + ); + + $: totalAmountSats = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 }).format( + amount + parseFloat(tipAmountSats.replace(/,/g, '')) + ); + + $: update(tipAmountSats); + let update = async (n) => { + let { id } = await post('/invoice', { + amount: amount + reverseFormat(n), + rate, + text, + username + }); + ({ text } = await get(`/invoice/${id}`)); + }; + + $: totalAmountFormatted = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(amountFiat + parseFloat(tipAmount)); + + const amountFormatted = new Intl.NumberFormat('en-US', { + maximumFractionDigits: 0 + }).format(amount); + + $: invoiceAmountFiatFormatted = new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD' + }).format(amountFiat); + + let customTipAmount; + + const tipAmounts = ['None', '10%', '15%', '20%']; + + const handleTipButtonClick = (amount) => { + customTipAmount = ''; + customInput.value = ''; + + if (amount === 'None') { + tipPercent = 0; + } else { + tipPercent = amount.slice(0, 2); + } + }; + + const handleCustomTipAmount = (e) => { + tipPercent = 0; + customTipAmount = e.target.value; + }; + + $: generate(text); + let generate = async (ready) => { + console.log('GENERATE', text); + if (!ready) return; + let { default: QRCodeStyling } = await import('qr-code-styling'); + + const qrCode = new QRCodeStyling({ + width: window.screen.width < 640 ? 250 : 300, + type: 'canvas', + data: text, + image: '/images/invoice.svg', + backgroundOptions: { + color: 'rgba(0, 0, 0, 0)' + }, + dotsOptions: { + type: 'rounded' + }, + imageOptions: { + hideBackgroundDots: false + } + }); + + qrCode.append(qr); + }; + + let showMobileTip = false; + + const handleBackButton = () => { + if (showMobileTip) { + tipPercent = 0; + customInput.value = ''; + customTipAmount = 0; + tipAmount = 0; + showMobileTip = false; + } else { + goto(`/${$user.username}/receive`); + } + }; + + let customInput; + + const handleCopy = () => { + copy(text); + toast.push('Copied!', { + theme: { + '--toastBarBackground': '#2F855A' + } + }); + }; + + const handleDoneClick = () => { + if ($user) { + goto(`/${$user.username}/receive`); + } else { + goto('/'); + } + }; +</script> + +{#if $invoices[id]?.status !== 'paid'} + <div class:full-shadow={showMobileTip}> + <button + class="ml-5 md:ml-20 mt-5 md:mt-10" + class:invisible={!$user} + on:click={handleBackButton} + > + <Icon icon="arrow-left" style="w-10" /> + </button> + + <div class="flex justify-center items-center mb-20 md:mt-[20px] px-3"> + <div + class="block lg:flex justify-center items-center space-y-10 lg:space-y-0 space-x-0 lg:space-x-24" + > + <!-- tipping section --> + <div + class="w-[100%] md:w-auto absolute {showMobileTip + ? 'bottom-0' + : '-bottom-full'} bg-white p-6 md:p-0 rounded-t-3xl md:rounded-none transition-all md:transition-none ease-in-out duration-500 left-0 md:static text-center space-y-5" + > + <h1 class="hidden md:block text-4xl font-semibold">Add a tip?</h1> + + <div> + <span class="font-semibold mr-1">{tipAmountFormatted}</span><span + class="text-secondary font-semibold text-sm">{`(${tipAmountSats} SATS)`}</span + > + <span class="block text-secondary text-sm">{tipPercent}%</span> + </div> + + <input + id="slider" + type="range" + min="0" + max="100" + value={tipPercent} + class="px-2" + on:input={(e) => { + customTipAmount = ''; + customInput.value = ''; + tipPercent = e.target.value; + }} + /> + + <div> + {#each tipAmounts as amount} + <button + class="bg-primary w-16 md:w-20 py-3 m-1 rounded-2xl font-semibold {customTipAmount + ? '' + : tipPercent === amount.slice(0, 2) || + ((tipPercent === 0 || tipPercent === '0') && amount === 'None') + ? '!bg-black text-white' + : ''}" + on:click={() => handleTipButtonClick(amount)}>{amount}</button + > + {/each} + + <div class="relative"> + <span + class="absolute top-[20px] left-5 font-semibold {!customTipAmount + ? 'opacity-50' + : 'opacity-100'}">$</span + > + <input + bind:this={customInput} + type="number" + min="0" + on:input={(e) => handleCustomTipAmount(e)} + class="bg-primary rounded-2xl py-3 px-10 font-semibold mt-2 mr-1 w-full md:w-auto" + placeholder="Other" + /> + <button + class="w-full md:w-auto mt-2 md:mt-0 bg-black text-white font-semibold py-3 px-7 rounded-2xl {showMobileTip + ? !customTipAmount && !tipAmount + ? 'opacity-50' + : 'opacity-100' + : !customTipAmount + ? 'opacity-50' + : 'opacity-100'}" + disabled={showMobileTip ? !customTipAmount && !tipAmount : !customTipAmount} + on:click={() => (showMobileTip = false)}>Done</button + > + </div> + </div> + </div> + + <div + class="hidden md:block h-[1px] lg:h-[600px] w-full lg:w-[1px] bg-lightgrey rounded-xl" + id="section-divider" + /> + + <!-- invoice section --> + <div class="text-center space-y-5"> + <button + class="block md:hidden bg-black {showMobileTip + ? 'text-white/50' + : 'text-white'} rounded-xl px-6 py-3 block text-sm font-semibold mx-auto flex justify-center items-center" + on:click={() => (showMobileTip = true)} + disabled={showMobileTip} + > + <svg + width="24" + height="24" + class="mr-1" + viewBox="0 0 24 24" + fill="none" + xmlns="http://www.w3.org/2000/svg" + > + <path + d="M4.31802 6.31802C2.56066 8.07538 2.56066 10.9246 4.31802 12.682L12.0001 20.364L19.682 12.682C21.4393 10.9246 21.4393 8.07538 19.682 6.31802C17.9246 4.56066 15.0754 4.56066 13.318 6.31802L12.0001 7.63609L10.682 6.31802C8.92462 4.56066 6.07538 4.56066 4.31802 6.31802Z" + stroke="currentColor" + stroke-width="2" + stroke-linecap="round" + stroke-linejoin="round" + /> + </svg> Add a tip + </button> + + <span class="text-secondary block" + >Scan to pay <span class="text-black font-semibold">{username}</span></span + > + + <div + bind:this={qr} + on:click={handleCopy} + class="border {showMobileTip + ? 'border-gray-400' + : 'border-lightgrey'} rounded-3xl block md:flex justify-center items-center cursor-pointer" + /> + + <div class="px-5 space-y-3"> + <div class="flex justify-between"> + <span class="font-semibold text-sm">Invoice</span> + <span class="font-semibold text-sm" + >{invoiceAmountFiatFormatted} + <span class="text-secondary font-normal">{`(${amountFormatted} SATS)`}</span></span + > + </div> + + <div class="flex justify-between"> + <span class="font-semibold text-sm">Tip</span> + <span class="font-semibold text-sm" + >{tipAmountFormatted} + <span class="text-secondary font-normal">{`(${tipAmountSats} SATS)`}</span></span + > + </div> + + <div class="flex justify-between"> + <span class="font-bold">Total</span> + <span class="font-bold" + >{totalAmountFormatted} + <span class="text-secondary font-normal">{`(${totalAmountSats} SATS)`}</span></span + > + </div> + </div> + </div> + </div> + </div> + </div> +{:else} + <div class="text-center mt-20 md:mt-0"> + <Image image="success" style="w-full md:w-3/4 xl:w-1/2 mx-auto max-w-3xl" /> + <h1 class="text-3xl md:text-4xl font-bold mb-6">Payment Successful!</h1> + <h2 class="text-2xl md:text-3xl font-semibold">{totalAmountFormatted} {$preferredCurrency}</h2> + <h3 class="text-secondary md:text-lg mb-6 mt-1">({totalAmountSats} SATS)</h3> + <button class="bg-black text-white rounded-2xl w-20 py-3 font-bold" on:click={handleDoneClick}> + Done + </button> + </div> +{/if} + +<style> + #slider { + -webkit-appearance: none; + width: 100%; + height: 6px; + border-radius: 5px; + background: #eaecef; + outline: none; + } + + #slider:active { + background-color: black; + } + + #slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 25px; + height: 25px; + border-radius: 50%; + background: black; + cursor: pointer; + } + + #slider::-moz-range-thumb { + width: 25px; + height: 25px; + border-radius: 50%; + background: black; + cursor: pointer; + } + + input[type='number']::-webkit-inner-spin-button, + input[type='number']::-webkit-outer-spin-button { + -webkit-appearance: none; + } + + input[type='number'] { + -moz-appearance: textfield; + } + + .full-shadow { + box-shadow: inset 0 0 0 1000px rgba(0, 0, 0, 0.25); + } +</style> diff --git a/src/routes/invoice/index.js b/src/routes/invoice/index.js new file mode 100644 index 00000000..cf26b3c6 --- /dev/null +++ b/src/routes/invoice/index.js @@ -0,0 +1,14 @@ +import { post } from '$lib/utils'; + +let network = 'lightning'; + +export async function POST({ request }) { + let { amount, rate, username, text } = await request.json(); + if (!text) ({ text } = await post('/lightning/invoice', { amount })); + let { uuid: id } = await post('/invoice', { + invoice: { amount, currency: 'USD', network, rate, text }, + user: { username } + }); + + return { body: { id } }; +} diff --git a/src/routes/jobs.svelte b/src/routes/jobs.svelte new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/routes/jobs.svelte diff --git a/src/routes/login.js b/src/routes/login.js index e8c74365..db1fbb6c 100644 --- a/src/routes/login.js +++ b/src/routes/login.js @@ -1,13 +1,24 @@ -import got from 'got'; +import { post } from '$lib/utils'; +import cookie from 'cookie'; + +let maxAge = 30 * 24 * 60 * 60; export async function POST({ request }) { - let json = await request.json(); + let body = await post('/login', await request.json()); - let body = await got - .post('http://localhost:3119/login', { - json - }) - .json(); + let expires = new Date(); + expires.setSeconds(expires.getSeconds() + maxAge); - return { body }; + return { + body, + headers: { + 'set-cookie': cookie.serialize('token', body.token, { + httpOnly: true, + maxAge, + sameSite: 'lax', + path: '/', + expires + }) + } + }; } diff --git a/src/routes/logout/done.svelte b/src/routes/logout/done.svelte new file mode 100644 index 00000000..fe526357 --- /dev/null +++ b/src/routes/logout/done.svelte @@ -0,0 +1,13 @@ +<script> + import { onMount } from 'svelte'; + import { user, token } from '$lib/store'; + import { goto } from '$app/navigation'; + import { close } from '$lib/socket'; + + onMount(() => { + $token = null; + $user = null; + close(); + }); + goto('/'); +</script> diff --git a/src/routes/logout/index.js b/src/routes/logout/index.js new file mode 100644 index 00000000..6cbae8f2 --- /dev/null +++ b/src/routes/logout/index.js @@ -0,0 +1,26 @@ +import cookie from 'cookie'; + +const opts = { + httpOnly: true, + sameSite: 'lax', + path: '/' +}; + +export async function GET({ request: { headers } }) { + return { + status: 302, + headers: { + location: '/logout/done', + 'set-cookie': [ + cookie.serialize('token', '', { + ...opts, + expires: new Date(0) + }), + cookie.serialize('refresh_token', '', { + ...opts, + expires: new Date(0) + }) + ] + } + }; +} diff --git a/src/routes/logout/index.svelte b/src/routes/logout/index.svelte new file mode 100644 index 00000000..2c6a7085 --- /dev/null +++ b/src/routes/logout/index.svelte @@ -0,0 +1,10 @@ +<script> + import { onMount } from 'svelte'; + import { browser } from '$app/env'; + import { user, token } from '$lib/store'; + + onMount(() => { + $user = null; + $token = null; + }); +</script> diff --git a/src/routes/receive.svelte b/src/routes/receive.svelte deleted file mode 100644 index 73d3cc06..00000000 --- a/src/routes/receive.svelte +++ /dev/null @@ -1,75 +0,0 @@ -<script> - // in the future we will want to allow users to set their fiat currency in the settings - import { AppHeader, Icon } from '$comp'; - import { goto } from '$app/navigation'; - - let useFiat = true; - let amountFiat = 0; - let amountSats = 0; - - const numPad = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '0', '<']; -</script> - -<AppHeader /> - -<!-- step 1 input amount and generate QR code --> -<div class="flex justify-center items-center mt-20 mb-3 px-3"> - <div class="space-y-5"> - <!-- amounts --> - <div class="text-center"> - <div class="text-6xl font-semibold tracking-widest mb-1"> - {useFiat ? `$${amountFiat}` : amountSats}<span class="tracking-normal text-base font-normal" - >{useFiat ? 'CAD' : 'SATS'}</span - > - </div> - <span class="text-secondary mr-1" - >{useFiat ? `${amountSats} SATS` : `$${amountFiat} CAD`}</span - > <button on:click={() => (useFiat = !useFiat)}><Icon icon="swap" style="inline" /></button> - </div> - - <!-- numpad --> - <div class="grid grid-cols-3 gap-2"> - {#each numPad as value} - {#if value === '<'} - <button - class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white" - > - <svg - class="w-5" - width="24" - height="24" - viewBox="0 0 24 24" - fill="none" - xmlns="http://www.w3.org/2000/svg" - > - <path - d="M15 19L8 12L15 5" - stroke="currentColor" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" - /> - </svg></button - > - {:else} - <button - class="bg-primary rounded-xl py-4 px-8 font-semibold active:bg-black active:text-white" - >{value}</button - > - {/if} - {/each} - </div> - - <!-- buttons --> - <div class="space-y-3"> - <button - class="bg-black text-white rounded-xl w-full h-[48px] flex justify-center items-center font-semibold text-sm" - on:click={() => goto('/invoice')}><Icon icon="qr" style="mr-2" /> Show QR</button - > - <button - class="bg-primary rounded-xl w-full h-[48px] flex justify-center items-center font-semibold text-sm" - ><Icon icon="send" style="mr-2" /> Send Invoice</button - > - </div> - </div> -</div> diff --git a/src/routes/register.js b/src/routes/register.js new file mode 100644 index 00000000..1bb03ba6 --- /dev/null +++ b/src/routes/register.js @@ -0,0 +1,14 @@ +import { post } from '$lib/utils'; + +export async function POST({ request }) { + try { + let user = await request.json(); + let body = await post('/register', { user }); + return { body }; + } catch (e) { + return { + status: 500, + body: e + }; + } +} diff --git a/src/routes/register.svelte b/src/routes/register.svelte index 60a81ced..69ec5fd2 100644 --- a/src/routes/register.svelte +++ b/src/routes/register.svelte @@ -1,6 +1,5 @@ <script> import { RegisterOrLogin } from '$comp'; - import { post } from "$lib/utils"; </script> <RegisterOrLogin page="Register" /> diff --git a/src/routes/support.svelte b/src/routes/support.svelte new file mode 100644 index 00000000..1935b156 --- /dev/null +++ b/src/routes/support.svelte @@ -0,0 +1,5 @@ +<script> + import { AppHeader } from '$comp'; +</script> + +<AppHeader /> diff --git a/static/icons/arrow-left.svg b/static/icons/arrow-left.svg new file mode 100644 index 00000000..64ded577 --- /dev/null +++ b/static/icons/arrow-left.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M10 19L3 12M3 12L10 5M3 12L21 12" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/close.svg b/static/icons/close.svg new file mode 100644 index 00000000..0fdb37c5 --- /dev/null +++ b/static/icons/close.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M6 18L18 6M6 6L18 18" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/copy.svg b/static/icons/copy.svg new file mode 100644 index 00000000..38b43e02 --- /dev/null +++ b/static/icons/copy.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M8 16H6C4.89543 16 4 15.1046 4 14V6C4 4.89543 4.89543 4 6 4H14C15.1046 4 16 4.89543 16 6V8M10 20H18C19.1046 20 20 19.1046 20 18V10C20 8.89543 19.1046 8 18 8H10C8.89543 8 8 8.89543 8 10V18C8 19.1046 8.89543 20 10 20Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/customers.svg b/static/icons/customers.svg new file mode 100644 index 00000000..0b6081ba --- /dev/null +++ b/static/icons/customers.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M12 4.35418C12.7329 3.52375 13.8053 3 15 3C17.2091 3 19 4.79086 19 7C19 9.20914 17.2091 11 15 11C13.8053 11 12.7329 10.4762 12 9.64582M15 21H3V20C3 16.6863 5.68629 14 9 14C12.3137 14 15 16.6863 15 20V21ZM15 21H21V20C21 16.6863 18.3137 14 15 14C13.9071 14 12.8825 14.2922 12 14.8027M13 7C13 9.20914 11.2091 11 9 11C6.79086 11 5 9.20914 5 7C5 4.79086 6.79086 3 9 3C11.2091 3 13 4.79086 13 7Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/dash.svg b/static/icons/dash.svg new file mode 100644 index 00000000..acb39ab9 --- /dev/null +++ b/static/icons/dash.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M9 19V13C9 11.8954 8.10457 11 7 11H5C3.89543 11 3 11.8954 3 13V19C3 20.1046 3.89543 21 5 21H7C8.10457 21 9 20.1046 9 19ZM9 19V9C9 7.89543 9.89543 7 11 7H13C14.1046 7 15 7.89543 15 9V19M9 19C9 20.1046 9.89543 21 11 21H13C14.1046 21 15 20.1046 15 19M15 19V5C15 3.89543 15.8954 3 17 3H19C20.1046 3 21 3.89543 21 5V19C21 20.1046 20.1046 21 19 21H17C15.8954 21 15 20.1046 15 19Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/error.svg b/static/icons/error.svg new file mode 100644 index 00000000..7d8269a4 --- /dev/null +++ b/static/icons/error.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M10 14L12 12M12 12L14 10M12 12L10 10M12 12L14 14M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/github.svg b/static/icons/github.svg new file mode 100644 index 00000000..039f4d21 --- /dev/null +++ b/static/icons/github.svg @@ -0,0 +1,3 @@ +<svg width="480" height="512" viewBox="0 0 480 512" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M186.1 328.7C186.1 349.6 175.2 383.8 149.4 383.8C123.6 383.8 112.7 349.6 112.7 328.7C112.7 307.8 123.6 273.6 149.4 273.6C175.2 273.6 186.1 307.8 186.1 328.7V328.7ZM480 278.2C480 310.1 476.8 343.9 462.5 373.2C424.6 449.8 320.4 448 245.8 448C170 448 59.6 450.7 20.2 373.2C5.6 344.2 0 310.1 0 278.2C0 236.3 13.9 196.7 41.5 164.6C36.3 148.8 33.8 132.2 33.8 115.8C33.8 94.3 38.7 83.5 48.4 64C93.7 64 122.7 73 157.2 100C186.2 93.1 216 90 245.9 90C272.9 90 300.1 92.9 326.3 99.2C360.3 72.5 389.3 64 434.1 64C443.9 83.5 448.7 94.3 448.7 115.8C448.7 132.2 446.1 148.5 441 164C468.5 196.4 480 236.3 480 278.2V278.2ZM415.7 328.7C415.7 284.8 389 246.1 342.2 246.1C323.3 246.1 305.2 249.5 286.2 252.1C271.3 254.4 256.4 255.3 241.1 255.3C225.9 255.3 211 254.4 196 252.1C177.3 249.5 159 246.1 140 246.1C93.2 246.1 66.5 284.8 66.5 328.7C66.5 416.5 146.9 430 216.9 430H265.1C335.4 430 415.7 416.6 415.7 328.7V328.7ZM333.1 273.6C307.3 273.6 296.4 307.8 296.4 328.7C296.4 349.6 307.3 383.8 333.1 383.8C358.9 383.8 369.8 349.6 369.8 328.7C369.8 307.8 358.9 273.6 333.1 273.6Z" fill="white"/> +</svg> diff --git a/static/icons/heart.svg b/static/icons/heart.svg new file mode 100644 index 00000000..f3a5f757 --- /dev/null +++ b/static/icons/heart.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M4.31802 6.31802C2.56066 8.07538 2.56066 10.9246 4.31802 12.682L12.0001 20.364L19.682 12.682C21.4393 10.9246 21.4393 8.07538 19.682 6.31802C17.9246 4.56066 15.0754 4.56066 13.318 6.31802L12.0001 7.63609L10.682 6.31802C8.92462 4.56066 6.07538 4.56066 4.31802 6.31802Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/info.svg b/static/icons/info.svg new file mode 100644 index 00000000..e916f53c --- /dev/null +++ b/static/icons/info.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M13 16H12V12H11M12 8H12.01M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/logout.svg b/static/icons/logout.svg new file mode 100644 index 00000000..a614cfc0 --- /dev/null +++ b/static/icons/logout.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M17 16L21 12M21 12L17 8M21 12L7 12M13 16V17C13 18.6569 11.6569 20 10 20H6C4.34315 20 3 18.6569 3 17V7C3 5.34315 4.34315 4 6 4H10C11.6569 4 13 5.34315 13 7V8" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/minus.svg b/static/icons/minus.svg new file mode 100644 index 00000000..65450cf4 --- /dev/null +++ b/static/icons/minus.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M15 12H9M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/plus.svg b/static/icons/plus.svg new file mode 100644 index 00000000..310c79d5 --- /dev/null +++ b/static/icons/plus.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M12 9V12M12 12V15M12 12H15M12 12H9M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/question.svg b/static/icons/question.svg new file mode 100644 index 00000000..b7d46339 --- /dev/null +++ b/static/icons/question.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M8.22766 9C8.77678 7.83481 10.2584 7 12.0001 7C14.2092 7 16.0001 8.34315 16.0001 10C16.0001 11.3994 14.7224 12.5751 12.9943 12.9066C12.4519 13.0106 12.0001 13.4477 12.0001 14M12 17H12.01M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/settings.svg b/static/icons/settings.svg new file mode 100644 index 00000000..2fac5bea --- /dev/null +++ b/static/icons/settings.svg @@ -0,0 +1,4 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M10.3246 4.31731C10.751 2.5609 13.249 2.5609 13.6754 4.31731C13.9508 5.45193 15.2507 5.99038 16.2478 5.38285C17.7913 4.44239 19.5576 6.2087 18.6172 7.75218C18.0096 8.74925 18.5481 10.0492 19.6827 10.3246C21.4391 10.751 21.4391 13.249 19.6827 13.6754C18.5481 13.9508 18.0096 15.2507 18.6172 16.2478C19.5576 17.7913 17.7913 19.5576 16.2478 18.6172C15.2507 18.0096 13.9508 18.5481 13.6754 19.6827C13.249 21.4391 10.751 21.4391 10.3246 19.6827C10.0492 18.5481 8.74926 18.0096 7.75219 18.6172C6.2087 19.5576 4.44239 17.7913 5.38285 16.2478C5.99038 15.2507 5.45193 13.9508 4.31731 13.6754C2.5609 13.249 2.5609 10.751 4.31731 10.3246C5.45193 10.0492 5.99037 8.74926 5.38285 7.75218C4.44239 6.2087 6.2087 4.44239 7.75219 5.38285C8.74926 5.99037 10.0492 5.45193 10.3246 4.31731Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +<path d="M15 12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12C9 10.3431 10.3431 9 12 9C13.6569 9 15 10.3431 15 12Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/shield.svg b/static/icons/shield.svg new file mode 100644 index 00000000..feda9661 --- /dev/null +++ b/static/icons/shield.svg @@ -0,0 +1,15 @@ +<svg width="96" height="122" viewBox="0 0 96 122" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M36.9721 93.8449C34.3435 92.6292 33.6075 92.6292 33.187 93.7392C32.6087 95.272 38.2338 99.1835 41.1252 99.1835C44.0167 99.1835 41.4932 95.9063 36.9721 93.8449Z" fill="black"/> +<path d="M20.5703 79.9963C19.3086 80.472 19.8869 81.6348 22.6732 84.172C27.2469 88.3477 29.8755 88.7177 27.8777 84.912C26.248 81.8991 22.3577 79.2563 20.5703 79.9963Z" fill="black"/> +<path d="M15.523 67.6268C14.6818 70.7454 15.8384 73.8116 17.8361 73.8116C19.6761 73.8116 20.2544 69.054 18.7298 67.0983C17.8361 65.9354 15.8384 66.3054 15.523 67.6268Z" fill="black"/> +<path d="M16.4172 25.3949C17.9943 21.272 15.6286 14.3478 13.4206 16.5678C12.7898 17.2021 13.0526 24.3377 13.7886 25.342C14.6298 26.452 15.9966 26.5049 16.4172 25.3949Z" fill="black"/> +<path d="M17.5732 58.2712C18.7298 57.8484 18.5726 52.5627 17.3634 51.4527C15.3657 49.6027 14.6292 50.2898 14.6292 53.9898C14.6292 57.5312 15.6281 58.9584 17.5732 58.2712Z" fill="black"/> +<path d="M16.8898 41.1987C17.5207 37.8158 16.2589 33.6406 14.6292 33.6406C13.7355 33.6406 13.6304 34.0101 13.6304 36.9701C13.5778 41.6215 16.1538 44.9515 16.8898 41.1987Z" fill="black"/> +<path d="M77.5572 35.5959C76.8738 36.2831 77.1892 40.9874 78.0304 41.7802C82.4989 46.1674 80.7641 32.3188 77.5572 35.5959Z" fill="black"/> +<path d="M81.395 67.2044C81.395 65.3016 78.9241 64.1387 77.8201 65.513C76.979 66.5173 77.347 69.6887 76.243 71.8559C75.4544 73.4416 75.7173 74.023 77.5047 74.5516C78.8716 74.9216 81.395 73.5473 81.395 67.2044Z" fill="black"/> +<path d="M79.555 26.7684C81.3424 26.7684 81.6052 26.1341 81.1847 22.2227C80.659 17.0958 77.8201 15.2455 76.7161 19.2627C75.4544 23.7027 76.611 26.7684 79.555 26.7684Z" fill="black"/> +<path d="M94.2229 8.84999C93.8023 2.08427 87.3355 0.12898 80.659 0.0232653C62.9949 -0.293878 67.6218 2.71898 5.90264 3.72327C2.59064 3.77612 1.43386 3.51173 0.960713 7.05316C0.802999 8.32173 2.22232 55.417 2.69546 69.107C3.01089 78.6213 3.79941 77.0356 8.11027 82.427C12.6841 88.1356 22.0944 100.028 38.6018 117.048C49.2738 128.043 58.2635 118.212 93.0658 77.459C93.9595 76.4018 94.4852 75.1333 94.5378 73.759C95.3264 59.4876 95.5898 30.3627 94.2229 8.84999ZM90.3321 73.019C90.2795 73.0718 52.376 116.943 49.0115 116.943C47.8023 116.943 33.6601 104.68 25.4064 95.219C18.9401 87.819 13.8407 82.7447 9.8978 78.7804C8.89895 77.7761 8.32092 76.4542 8.26835 75.027C7.90035 63.9799 6.27027 15.0343 6.27027 14.9286C5.42913 8.47999 5.11365 8.5858 27.0361 8.69152C58.8418 8.90295 66.5698 5.67877 89.5961 5.8902C89.5435 49.709 91.9618 72.0676 90.3321 73.019Z" fill="black"/> +<path d="M70.1447 80.2073C68.7252 81.3702 66.1492 85.0174 66.1492 85.9159C66.1492 87.8188 70.9858 85.3345 72.7207 82.5859C74.8235 79.1502 73.2464 77.6702 70.1447 80.2073Z" fill="black"/> +<path d="M54.6361 96.0114C51.7446 99.5 51.64 99.7114 52.5863 100.663C54.1109 102.196 61.2601 96.4877 60.7869 94.1091C60.3664 91.6777 57.5275 92.5228 54.6361 96.0114Z" fill="black"/> +<path d="M78.8715 59.1699C80.6064 59.8042 81.6058 55.1004 80.2915 52.6161C79.5029 51.189 77.347 51.2941 76.979 52.827C76.1904 55.9984 76.9264 58.4299 78.8715 59.1699Z" fill="black"/> +</svg> diff --git a/static/icons/support.svg b/static/icons/support.svg new file mode 100644 index 00000000..47b935b5 --- /dev/null +++ b/static/icons/support.svg @@ -0,0 +1,3 @@ +<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M7 8H17M7 12H11M12 20L8 16H5C3.89543 16 3 15.1046 3 14V6C3 4.89543 3.89543 4 5 4H19C20.1046 4 21 4.89543 21 6V14C21 15.1046 20.1046 16 19 16H16L12 20Z" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/> +</svg> diff --git a/static/icons/twitter.svg b/static/icons/twitter.svg new file mode 100644 index 00000000..e43e5f2a --- /dev/null +++ b/static/icons/twitter.svg @@ -0,0 +1,3 @@ +<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M459.37 151.716C459.695 156.264 459.695 160.813 459.695 165.361C459.695 304.081 354.112 463.919 161.137 463.919C101.685 463.919 46.457 446.7 0 416.813C8.447 417.787 16.568 418.112 25.34 418.112C74.395 418.112 119.553 401.544 155.614 373.28C109.482 372.305 70.822 342.092 57.502 300.508C64 301.482 70.497 302.132 77.32 302.132C86.741 302.132 96.163 300.832 104.934 298.559C56.853 288.812 20.791 246.579 20.791 195.574V194.275C34.76 202.072 51.005 206.945 68.222 207.594C39.958 188.751 21.441 156.589 21.441 120.203C21.441 100.711 26.638 82.843 35.735 67.249C87.39 130.924 165.035 172.507 252.1 177.056C250.476 169.259 249.501 161.138 249.501 153.016C249.501 95.188 296.283 48.082 354.435 48.082C384.648 48.082 411.937 60.752 431.105 81.219C454.82 76.671 477.561 67.899 497.704 55.879C489.906 80.245 473.338 100.712 451.572 113.706C472.689 111.433 493.156 105.584 511.998 97.463C497.706 118.254 479.837 136.771 459.37 151.716V151.716Z" fill="white"/> +</svg> diff --git a/static/images/invoice.svg b/static/images/invoice.svg new file mode 100644 index 00000000..4c9cbce9 --- /dev/null +++ b/static/images/invoice.svg @@ -0,0 +1,4 @@ +<svg width="72" height="72" viewBox="0 0 72 72" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="72" height="72" rx="24" fill="#FA9514"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M39.1208 14.5116C40.1198 14.8265 40.7992 15.753 40.7992 16.8006V28.8006L50.3992 28.8006C51.2941 28.8006 52.1147 29.2985 52.528 30.0922C52.9412 30.886 52.8786 31.8437 52.3654 32.5769L35.5654 56.5769C34.9647 57.435 33.8768 57.8045 32.8777 57.4895C31.8786 57.1746 31.1992 56.2481 31.1992 55.2006L31.1992 43.2006H21.5992C20.7043 43.2006 19.8838 42.7027 19.4705 41.9089C19.0572 41.1152 19.1199 40.1574 19.6331 39.4243L36.4331 15.4243C37.0338 14.5661 38.1217 14.1967 39.1208 14.5116Z" fill="white"/> +</svg> diff --git a/static/images/success.png b/static/images/success.png Binary files differnew file mode 100644 index 00000000..d547edd9 --- /dev/null +++ b/static/images/success.png diff --git a/static/images/swoosh.png b/static/images/swoosh.png Binary files differnew file mode 100644 index 00000000..b97bf475 --- /dev/null +++ b/static/images/swoosh.png diff --git a/tailwind.config.cjs b/tailwind.config.cjs index 86d5d1cf..d8b7f6d4 100644 --- a/tailwind.config.cjs +++ b/tailwind.config.cjs @@ -5,8 +5,9 @@ module.exports = { extend: { colors: { primary: '#F5F7FA', - secondary: '#858A92', - gradient: '#C3CFE2' + secondary: '#70757E', + gradient: '#C3CFE2', + lightgrey: '#EAECEF' } } }, |
