diff options
114 files changed, 1281 insertions, 540 deletions
@@ -3,3 +3,4 @@ node_modules /build /.svelte-kit /package +.env @@ -6,8 +6,8 @@ ENV NODE_ENV $NODE_ENV RUN npm i -g pnpm WORKDIR /app -COPY package.json . -RUN NODE_ENV=development pnpm i +ADD package.json . +RUN --mount=type=cache,target=/root/.local/share/pnpm/store NODE_ENV=development pnpm i COPY . . RUN pnpm build diff --git a/package.json b/package.json index 808e5761..b00befb7 100644 --- a/package.json +++ b/package.json @@ -11,29 +11,28 @@ }, "devDependencies": { "@sveltejs/adapter-node": "next", - "@sveltejs/kit": "1.0.0-next.505", + "@sveltejs/kit": "1.0.0-next.516", "@zerodevx/svelte-toast": "^0.7.2", "autoprefixer": "^10.4.12", "bip21": "^2.0.3", "bitcoin-address-validation": "^2.2.1", "bolt11": "^1.4.0", - "cookie": "^0.5.0", "date-fns": "^2.29.3", - "leaflet": "^1.9.1", + "leaflet": "^1.9.2", "leaflet.markercluster": "^1.5.3", - "postcss": "^8.4.17", + "postcss": "^8.4.18", "prettier": "^2.7.1", - "prettier-plugin-svelte": "^2.7.1", + "prettier-plugin-svelte": "^2.8.0", "qr-code-styling": "1.6.0-rc.1", "qr-scanner": "^1.4.1", "screenfull": "^6.0.2", - "svelte": "^3.50.1", - "svelte-outclick": "^3.1.0", + "svelte": "^3.52.0", + "svelte-outclick": "^3.2.0", "svelte-pincode": "^2.1.2", "svelte-preprocess": "^4.10.7", "sveltekit-i18n": "^2.2.2", "tailwindcss": "^3.1.8", - "vite": "^3.1.4" + "vite": "^3.1.8" }, "type": "module" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 644cca0e..04bec42b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,60 +2,58 @@ lockfileVersion: 5.4 specifiers: '@sveltejs/adapter-node': next - '@sveltejs/kit': 1.0.0-next.505 + '@sveltejs/kit': 1.0.0-next.516 '@zerodevx/svelte-toast': ^0.7.2 autoprefixer: ^10.4.12 bip21: ^2.0.3 bitcoin-address-validation: ^2.2.1 bolt11: ^1.4.0 - cookie: ^0.5.0 date-fns: ^2.29.3 - leaflet: ^1.9.1 + leaflet: ^1.9.2 leaflet.markercluster: ^1.5.3 - postcss: ^8.4.17 + postcss: ^8.4.18 prettier: ^2.7.1 - prettier-plugin-svelte: ^2.7.1 + prettier-plugin-svelte: ^2.8.0 qr-code-styling: 1.6.0-rc.1 qr-scanner: ^1.4.1 screenfull: ^6.0.2 - svelte: ^3.50.1 - svelte-outclick: ^3.1.0 + svelte: ^3.52.0 + svelte-outclick: ^3.2.0 svelte-pincode: ^2.1.2 svelte-preprocess: ^4.10.7 sveltekit-i18n: ^2.2.2 tailwindcss: ^3.1.8 - vite: ^3.1.4 + vite: ^3.1.8 devDependencies: - '@sveltejs/adapter-node': 1.0.0-next.96 - '@sveltejs/kit': [email protected][email protected] + '@sveltejs/adapter-node': 1.0.0-next.98 + '@sveltejs/kit': [email protected][email protected] '@zerodevx/svelte-toast': 0.7.2 - autoprefixer: [email protected] + autoprefixer: [email protected] bip21: 2.0.3 bitcoin-address-validation: 2.2.1 bolt11: 1.4.0 - cookie: 0.5.0 date-fns: 2.29.3 - leaflet: 1.9.1 - leaflet.markercluster: [email protected] - postcss: 8.4.17 + leaflet: 1.9.2 + leaflet.markercluster: [email protected] + postcss: 8.4.18 prettier: 2.7.1 - prettier-plugin-svelte: 2.7.1_nk6d2fkgcllkkdynqbuearcure + prettier-plugin-svelte: 2.8.0_lrllcp5xtrkmmdzifit4hd52ze qr-code-styling: 1.6.0-rc.1 qr-scanner: 1.4.1 screenfull: 6.0.2 - svelte: 3.50.1 - svelte-outclick: 3.1.0 + svelte: 3.52.0 + svelte-outclick: 3.2.0 svelte-pincode: 2.1.2 - svelte-preprocess: 4.10.7_r763xggw3my2ousygwguxmp6ru - sveltekit-i18n: [email protected] - tailwindcss: [email protected] - vite: 3.1.4 + svelte-preprocess: 4.10.7_vidagnlh55bt6yxji7dk5y5wla + sveltekit-i18n: [email protected] + tailwindcss: [email protected] + vite: 3.1.8 packages: - /@esbuild/android-arm/0.15.10: - resolution: {integrity: sha512-FNONeQPy/ox+5NBkcSbYJxoXj9GWu8gVGJTVmUyoOCKQFDTrHVKgNSzChdNt0I8Aj/iKcsDf2r9BFwv+FSNUXg==} + /@esbuild/android-arm/0.15.11: + resolution: {integrity: sha512-PzMcQLazLBkwDEkrNPi9AbjFt6+3I7HKbiYF2XtWQ7wItrHvEOeO3T8Am434zAozWtVP7lrTue1bEfc2nYWeCA==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -63,8 +61,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64/0.15.10: - resolution: {integrity: sha512-w0Ou3Z83LOYEkwaui2M8VwIp+nLi/NA60lBLMvaJ+vXVMcsARYdEzLNE7RSm4+lSg4zq4d7fAVuzk7PNQ5JFgg==} + /@esbuild/linux-loong64/0.15.11: + resolution: {integrity: sha512-geWp637tUhNmhL3Xgy4Bj703yXB9dqiLJe05lCUfjSFDrQf9C/8pArusyPUbUbPwlC/EAUjBw32sxuIl/11dZw==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -97,39 +95,48 @@ packages: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true - /@rollup/plugin-commonjs/[email protected]: - resolution: {integrity: sha512-//NdP6iIwPbMTcazYsiBMbJW7gfmpHom33u1beiIoHDEM0Q9clvtQB1T0efvMqHeKsGohiHo97BCPCkBXdscwg==} - engines: {node: '>= 12.0.0'} + /@rollup/plugin-commonjs/[email protected]: + resolution: {integrity: sha512-JbrTRyDNtLQj/rhl7RFUuYXwQ2fac+33oLDAu2k++WD95zweyo28UAomLVA0JMGx4vmCa7Nw4T6k/1F6lelExg==} + engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.68.0 + rollup: ^2.68.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: - '@rollup/pluginutils': [email protected] + '@rollup/pluginutils': 4.2.1 commondir: 1.0.1 estree-walker: 2.0.2 - glob: 7.2.3 + glob: 8.0.3 is-reference: 1.2.1 - magic-string: 0.25.9 - resolve: 1.22.1 + magic-string: 0.26.7 rollup: 2.79.1 dev: true - /@rollup/plugin-json/[email protected]: - resolution: {integrity: sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw==} + /@rollup/plugin-json/[email protected]: + resolution: {integrity: sha512-LsWDA5wJs/ggzakVuKQhZo7HPRcQZgBa3jWIVxQSFxaRToUGNi8ZBh3+k/gQ+1eInVYJgn4WBRCUkmoDrmmGzw==} + engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^1.20.0 || ^2.0.0 + rollup: ^1.20.0||^2.0.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: - '@rollup/pluginutils': [email protected] + '@rollup/pluginutils': 4.2.1 rollup: 2.79.1 dev: true - /@rollup/plugin-node-resolve/[email protected]: - resolution: {integrity: sha512-5G2niJroNCz/1zqwXtk0t9+twOSDlG00k1Wfd7bkbbXmwg8H8dvgHdIWAun53Ps/rckfvOC7scDBjuGFg5OaWw==} - engines: {node: '>= 10.0.0'} + /@rollup/plugin-node-resolve/[email protected]: + resolution: {integrity: sha512-iwJbzfTzlzDDQcGmkS7EkCKwe2kSkdBrjX87Fy/KrNjr6UNnLpod0t6X66e502LRe5JJCA4FFqrEscWPnZAkig==} + engines: {node: '>=14.0.0'} peerDependencies: - rollup: ^2.78.0 + rollup: ^2.78.0||^3.0.0 + peerDependenciesMeta: + rollup: + optional: true dependencies: - '@rollup/pluginutils': [email protected] - '@types/resolve': 1.17.1 + '@rollup/pluginutils': 4.2.1 + '@types/resolve': 1.20.2 deepmerge: 4.2.2 is-builtin-module: 3.2.0 is-module: 1.0.0 @@ -137,18 +144,6 @@ packages: rollup: 2.79.1 dev: true - /@rollup/pluginutils/[email protected]: - resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} - engines: {node: '>= 8.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0 - dependencies: - '@types/estree': 0.0.39 - estree-walker: 1.0.1 - picomatch: 2.3.1 - rollup: 2.79.1 - dev: true - /@rollup/pluginutils/4.2.1: resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} engines: {node: '>= 8.0.0'} @@ -157,17 +152,17 @@ packages: picomatch: 2.3.1 dev: true - /@sveltejs/adapter-node/1.0.0-next.96: - resolution: {integrity: sha512-tIHaRolUYy2PiHl4RUWaOsYxEjK5lN9501qzCzFbYr/uoLnZcnPGSXNJICwX0AX9AUkV6cvkZey6bLbUQcwH0Q==} + /@sveltejs/adapter-node/1.0.0-next.98: + resolution: {integrity: sha512-eo89sswzm3ntZJgaQzz2MneqMqBfQWQj6b8VtImlHeq2AB+RSodippmKY0RyPcIJ0hJqdDc4DubU7XMDKkoTew==} dependencies: - '@rollup/plugin-commonjs': [email protected] - '@rollup/plugin-json': [email protected] - '@rollup/plugin-node-resolve': [email protected] + '@rollup/plugin-commonjs': [email protected] + '@rollup/plugin-json': [email protected] + '@rollup/plugin-node-resolve': [email protected] rollup: 2.79.1 dev: true - /@sveltejs/kit/[email protected][email protected]: - resolution: {integrity: sha512-vg2WvWiUB7useJZOKbdM8WOXv10WD7SVDER74IXd1t/TWb/uXHzwMQ/ZAsXZFdxQsz1cDGFub3iC76PW+j58Pg==} + /@sveltejs/kit/[email protected][email protected]: + resolution: {integrity: sha512-n0oGcv7xpgJ81ld1oER5HVREP4TdeDUJ8S64XNDcl3Y2xfQLKk8C4SLYQw2D6V+DxUm8V3aRrj7N7/tm4CQm6A==} engines: {node: '>=16.14'} hasBin: true requiresBuild: true @@ -175,28 +170,27 @@ packages: svelte: ^3.44.0 vite: ^3.1.0 dependencies: - '@sveltejs/vite-plugin-svelte': [email protected][email protected] + '@sveltejs/vite-plugin-svelte': [email protected][email protected] '@types/cookie': 0.5.1 cookie: 0.5.0 - devalue: 3.1.3 + devalue: 4.0.1 kleur: 4.1.5 - magic-string: 0.26.5 + magic-string: 0.26.7 mime: 3.0.0 - node-fetch: 3.2.10 sade: 1.8.1 set-cookie-parser: 2.5.1 sirv: 2.0.2 - svelte: 3.50.1 + svelte: 3.52.0 tiny-glob: 0.2.9 undici: 5.11.0 - vite: 3.1.4 + vite: 3.1.8 transitivePeerDependencies: - diff-match-patch - supports-color dev: true - /@sveltejs/vite-plugin-svelte/[email protected][email protected]: - resolution: {integrity: sha512-1xkVTB4pm6zuign858FzVYE9Fdw9MQBOlxrdd85STV0NvTDmcofcRpcrK+zcIyT8SZ2dseHLu8hvDwzssF6RfA==} + /@sveltejs/vite-plugin-svelte/[email protected][email protected]: + resolution: {integrity: sha512-+SDrAnT7TDi8sdj4OfD2SC4s9DNrpNVBrue8fT2PmKks9Ddu0JIfSeX91wXZb/1xHz4EkGb+rli8GTRI0yGOjg==} engines: {node: ^14.18.0 || >= 16} peerDependencies: diff-match-patch: ^1.0.5 @@ -210,20 +204,20 @@ packages: debug: 4.3.4 deepmerge: 4.2.2 kleur: 4.1.5 - magic-string: 0.26.5 - svelte: 3.50.1 - svelte-hmr: [email protected] - vite: 3.1.4 + magic-string: 0.26.7 + svelte: 3.52.0 + svelte-hmr: [email protected] + vite: 3.1.8 transitivePeerDependencies: - supports-color dev: true - /@sveltekit-i18n/base/[email protected]: + /@sveltekit-i18n/base/[email protected]: resolution: {integrity: sha512-F8gqG2+KAOeT0o2wYlUrW3TRCX7zaD7rBy/1CEVNw0irfw9TgFf/ODmhubkHHT3+6Zk+SMz8RNgeuffBfAMbJw==} peerDependencies: svelte: ^3.x dependencies: - svelte: 3.50.1 + svelte: 3.52.0 optionalDependencies: '@sveltekit-i18n/parser-default': 1.0.3 dev: true @@ -235,23 +229,19 @@ packages: /@types/bn.js/4.11.6: resolution: {integrity: sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==} dependencies: - '@types/node': 18.8.0 + '@types/node': 18.11.0 dev: true /@types/cookie/0.5.1: resolution: {integrity: sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==} dev: true - /@types/estree/0.0.39: - resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} - dev: true - /@types/estree/1.0.0: resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} dev: true - /@types/node/18.8.0: - resolution: {integrity: sha512-u+h43R6U8xXDt2vzUaVP3VwjjLyOJk6uEciZS8OSyziUQGOwmk+l+4drxcsDboHXwyTaqS1INebghmWMRxq3LA==} + /@types/node/18.11.0: + resolution: {integrity: sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w==} dev: true /@types/offscreencanvas/2019.7.0: @@ -262,16 +252,14 @@ packages: resolution: {integrity: sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==} dev: true - /@types/resolve/1.17.1: - resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} - dependencies: - '@types/node': 18.8.0 + /@types/resolve/1.20.2: + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} dev: true /@types/sass/1.43.1: resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==} dependencies: - '@types/node': 18.8.0 + '@types/node': 18.11.0 dev: true /@zerodevx/svelte-toast/0.7.2: @@ -309,7 +297,7 @@ packages: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} dev: true - /autoprefixer/[email protected]: + /autoprefixer/[email protected]: resolution: {integrity: sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==} engines: {node: ^10 || ^12 || >=14} hasBin: true @@ -317,11 +305,11 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.4 - caniuse-lite: 1.0.30001414 + caniuse-lite: 1.0.30001420 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 - postcss: 8.4.17 + postcss: 8.4.18 postcss-value-parser: 4.2.0 dev: true @@ -410,6 +398,12 @@ packages: concat-map: 0.0.1 dev: true + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + /braces/3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -426,10 +420,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001414 - electron-to-chromium: 1.4.271 + caniuse-lite: 1.0.30001420 + electron-to-chromium: 1.4.283 node-releases: 2.0.6 - update-browserslist-db: [email protected] + update-browserslist-db: [email protected] dev: true /bs58/4.0.1: @@ -474,8 +468,8 @@ packages: engines: {node: '>= 6'} dev: true - /caniuse-lite/1.0.30001414: - resolution: {integrity: sha512-t55jfSaWjCdocnFdKQoO+d2ct9C59UZg4dY3OnUlSZ447r8pUtIKdp0hpAzrGFultmTC+Us+KpKi4GZl/LXlFg==} + /caniuse-lite/1.0.30001420: + resolution: {integrity: sha512-OnyeJ9ascFA9roEj72ok2Ikp7PHJTKubtEJIQ/VK3fdsS50q4KWy+Z5X0A1/GswEItKX0ctAp8n4SYDE7wTu6A==} dev: true /chokidar/3.5.3: @@ -533,11 +527,6 @@ packages: hasBin: true dev: true - /data-uri-to-buffer/4.0.0: - resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} - engines: {node: '>= 12'} - dev: true - /date-fns/2.29.3: resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==} engines: {node: '>=0.11'} @@ -560,8 +549,8 @@ packages: engines: {node: '>=0.10.0'} dev: true - /defined/1.0.0: - resolution: {integrity: sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ==} + /defined/1.0.1: + resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} dev: true /detect-indent/6.1.0: @@ -575,12 +564,12 @@ packages: hasBin: true dependencies: acorn-node: 1.8.2 - defined: 1.0.0 - minimist: 1.2.6 + defined: 1.0.1 + minimist: 1.2.7 dev: true - /devalue/3.1.3: - resolution: {integrity: sha512-9KO89Cb+qjzf2CqdrH+NuLaqdk9GhDP5EhR4zlkR51dvuIaiqtlkDkGzLMShDemwUy21raSMdu+kpX8Enw3yGQ==} + /devalue/4.0.1: + resolution: {integrity: sha512-Oksbel8g2rv5ivcCyImF1RXEU2FcS1OtCwVs4tJCCeVws/Dp9EE15fUbEsNr/xLD3ZxsQURBCDf56Lk1CgwCpg==} dev: true /didyoumean/1.2.2: @@ -591,8 +580,8 @@ packages: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} dev: true - /electron-to-chromium/1.4.271: - resolution: {integrity: sha512-BCPBtK07xR1/uY2HFDtl3wK2De66AW4MSiPlLrnPNxKC/Qhccxd59W73654S3y6Rb/k3hmuGJOBnhjfoutetXA==} + /electron-to-chromium/1.4.283: + resolution: {integrity: sha512-g6RQ9zCOV+U5QVHW9OpFR7rdk/V7xfopNXnyAamdpFgCHgZ1sjI8VuR1+zG2YG/TZk+tQ8mpNkug4P8FU0fuOA==} dev: true /elliptic/6.5.4: @@ -611,8 +600,8 @@ packages: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} dev: true - /esbuild-android-64/0.15.10: - resolution: {integrity: sha512-UI7krF8OYO1N7JYTgLT9ML5j4+45ra3amLZKx7LO3lmLt1Ibn8t3aZbX5Pu4BjWiqDuJ3m/hsvhPhK/5Y/YpnA==} + /esbuild-android-64/0.15.11: + resolution: {integrity: sha512-rrwoXEiuI1kaw4k475NJpexs8GfJqQUKcD08VR8sKHmuW9RUuTR2VxcupVvHdiGh9ihxL9m3lpqB1kju92Ialw==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -620,8 +609,8 @@ packages: dev: true optional: true - /esbuild-android-arm64/0.15.10: - resolution: {integrity: sha512-EOt55D6xBk5O05AK8brXUbZmoFj4chM8u3riGflLa6ziEoVvNjRdD7Cnp82NHQGfSHgYR06XsPI8/sMuA/cUwg==} + /esbuild-android-arm64/0.15.11: + resolution: {integrity: sha512-/hDubOg7BHOhUUsT8KUIU7GfZm5bihqssvqK5PfO4apag7YuObZRZSzViyEKcFn2tPeHx7RKbSBXvAopSHDZJQ==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -629,8 +618,8 @@ packages: dev: true optional: true - /esbuild-darwin-64/0.15.10: - resolution: {integrity: sha512-hbDJugTicqIm+WKZgp208d7FcXcaK8j2c0l+fqSJ3d2AzQAfjEYDRM3Z2oMeqSJ9uFxyj/muSACLdix7oTstRA==} + /esbuild-darwin-64/0.15.11: + resolution: {integrity: sha512-1DqHD0ms3AhiwkKnjRUzmiW7JnaJJr5FKrPiR7xuyMwnjDqvNWDdMq4rKSD9OC0piFNK6n0LghsglNMe2MwJtA==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -638,8 +627,8 @@ packages: dev: true optional: true - /esbuild-darwin-arm64/0.15.10: - resolution: {integrity: sha512-M1t5+Kj4IgSbYmunf2BB6EKLkWUq+XlqaFRiGOk8bmBapu9bCDrxjf4kUnWn59Dka3I27EiuHBKd1rSO4osLFQ==} + /esbuild-darwin-arm64/0.15.11: + resolution: {integrity: sha512-OMzhxSbS0lwwrW40HHjRCeVIJTURdXFA8c3GU30MlHKuPCcvWNUIKVucVBtNpJySXmbkQMDJdJNrXzNDyvoqvQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -647,8 +636,8 @@ packages: dev: true optional: true - /esbuild-freebsd-64/0.15.10: - resolution: {integrity: sha512-KMBFMa7C8oc97nqDdoZwtDBX7gfpolkk6Bcmj6YFMrtCMVgoU/x2DI1p74DmYl7CSS6Ppa3xgemrLrr5IjIn0w==} + /esbuild-freebsd-64/0.15.11: + resolution: {integrity: sha512-8dKP26r0/Qyez8nTCwpq60QbuYKOeBygdgOAWGCRalunyeqWRoSZj9TQjPDnTTI9joxd3QYw3UhVZTKxO9QdRg==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -656,8 +645,8 @@ packages: dev: true optional: true - /esbuild-freebsd-arm64/0.15.10: - resolution: {integrity: sha512-m2KNbuCX13yQqLlbSojFMHpewbn8wW5uDS6DxRpmaZKzyq8Dbsku6hHvh2U+BcLwWY4mpgXzFUoENEf7IcioGg==} + /esbuild-freebsd-arm64/0.15.11: + resolution: {integrity: sha512-aSGiODiukLGGnSg/O9+cGO2QxEacrdCtCawehkWYTt5VX1ni2b9KoxpHCT9h9Y6wGqNHmXFnB47RRJ8BIqZgmQ==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -665,8 +654,8 @@ packages: dev: true optional: true - /esbuild-linux-32/0.15.10: - resolution: {integrity: sha512-guXrwSYFAvNkuQ39FNeV4sNkNms1bLlA5vF1H0cazZBOLdLFIny6BhT+TUbK/hdByMQhtWQ5jI9VAmPKbVPu1w==} + /esbuild-linux-32/0.15.11: + resolution: {integrity: sha512-lsrAfdyJBGx+6aHIQmgqUonEzKYeBnyfJPkT6N2dOf1RoXYYV1BkWB6G02tjsrz1d5wZzaTc3cF+TKmuTo/ZwA==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -674,8 +663,8 @@ packages: dev: true optional: true - /esbuild-linux-64/0.15.10: - resolution: {integrity: sha512-jd8XfaSJeucMpD63YNMO1JCrdJhckHWcMv6O233bL4l6ogQKQOxBYSRP/XLWP+6kVTu0obXovuckJDcA0DKtQA==} + /esbuild-linux-64/0.15.11: + resolution: {integrity: sha512-Y2Rh+PcyVhQqXKBTacPCltINN3uIw2xC+dsvLANJ1SpK5NJUtxv8+rqWpjmBgaNWKQT1/uGpMmA9olALy9PLVA==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -683,8 +672,8 @@ packages: dev: true optional: true - /esbuild-linux-arm/0.15.10: - resolution: {integrity: sha512-6N8vThLL/Lysy9y4Ex8XoLQAlbZKUyExCWyayGi2KgTBelKpPgj6RZnUaKri0dHNPGgReJriKVU6+KDGQwn10A==} + /esbuild-linux-arm/0.15.11: + resolution: {integrity: sha512-TJllTVk5aSyqPFvvcHTvf6Wu1ZKhWpJ/qNmZO8LL/XeB+LXCclm7HQHNEIz6MT7IX8PmlC1BZYrOiw2sXSB95A==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -692,8 +681,8 @@ packages: dev: true optional: true - /esbuild-linux-arm64/0.15.10: - resolution: {integrity: sha512-GByBi4fgkvZFTHFDYNftu1DQ1GzR23jws0oWyCfhnI7eMOe+wgwWrc78dbNk709Ivdr/evefm2PJiUBMiusS1A==} + /esbuild-linux-arm64/0.15.11: + resolution: {integrity: sha512-uhcXiTwTmD4OpxJu3xC5TzAAw6Wzf9O1XGWL448EE9bqGjgV1j+oK3lIHAfsHnuIn8K4nDW8yjX0Sv5S++oRuw==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -701,8 +690,8 @@ packages: dev: true optional: true - /esbuild-linux-mips64le/0.15.10: - resolution: {integrity: sha512-BxP+LbaGVGIdQNJUNF7qpYjEGWb0YyHVSKqYKrn+pTwH/SiHUxFyJYSP3pqkku61olQiSBnSmWZ+YUpj78Tw7Q==} + /esbuild-linux-mips64le/0.15.11: + resolution: {integrity: sha512-WD61y/R1M4BLe4gxXRypoQ0Ci+Vjf714QYzcPNkiYv5I8K8WDz2ZR8Bm6cqKxd6rD+e/rZgPDbhQ9PCf7TMHmA==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -710,8 +699,8 @@ packages: dev: true optional: true - /esbuild-linux-ppc64le/0.15.10: - resolution: {integrity: sha512-LoSQCd6498PmninNgqd/BR7z3Bsk/mabImBWuQ4wQgmQEeanzWd5BQU2aNi9mBURCLgyheuZS6Xhrw5luw3OkQ==} + /esbuild-linux-ppc64le/0.15.11: + resolution: {integrity: sha512-JVleZS9oPVLTlBhPTWgOwxFWU/wMUdlBwTbGA4GF8c38sLbS13cupj+C8bLq929jU7EMWry4SaL+tKGIaTlqKg==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -719,8 +708,8 @@ packages: dev: true optional: true - /esbuild-linux-riscv64/0.15.10: - resolution: {integrity: sha512-Lrl9Cr2YROvPV4wmZ1/g48httE8z/5SCiXIyebiB5N8VT7pX3t6meI7TQVHw/wQpqP/AF4SksDuFImPTM7Z32Q==} + /esbuild-linux-riscv64/0.15.11: + resolution: {integrity: sha512-9aLIalZ2HFHIOZpmVU11sEAS9F8TnHw49daEjcgMpBXHFF57VuT9f9/9LKJhw781Gda0P9jDkuCWJ0tFbErvJw==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -728,8 +717,8 @@ packages: dev: true optional: true - /esbuild-linux-s390x/0.15.10: - resolution: {integrity: sha512-ReP+6q3eLVVP2lpRrvl5EodKX7EZ1bS1/z5j6hsluAlZP5aHhk6ghT6Cq3IANvvDdscMMCB4QEbI+AjtvoOFpA==} + /esbuild-linux-s390x/0.15.11: + resolution: {integrity: sha512-sZHtiXXOKsLI3XGBGoYO4qKBzJlb8xNsWmvFiwFMHFzA4AXgDP1KDp7Dawe9C2pavTRBDvl+Ok4n/DHQ59oaTg==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -737,8 +726,8 @@ packages: dev: true optional: true - /esbuild-netbsd-64/0.15.10: - resolution: {integrity: sha512-iGDYtJCMCqldMskQ4eIV+QSS/CuT7xyy9i2/FjpKvxAuCzrESZXiA1L64YNj6/afuzfBe9i8m/uDkFHy257hTw==} + /esbuild-netbsd-64/0.15.11: + resolution: {integrity: sha512-hUC9yN06K9sg7ju4Vgu9ChAPdsEgtcrcLfyNT5IKwKyfpLvKUwCMZSdF+gRD3WpyZelgTQfJ+pDx5XFbXTlB0A==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -746,8 +735,8 @@ packages: dev: true optional: true - /esbuild-openbsd-64/0.15.10: - resolution: {integrity: sha512-ftMMIwHWrnrYnvuJQRJs/Smlcb28F9ICGde/P3FUTCgDDM0N7WA0o9uOR38f5Xe2/OhNCgkjNeb7QeaE3cyWkQ==} + /esbuild-openbsd-64/0.15.11: + resolution: {integrity: sha512-0bBo9SQR4t66Wd91LGMAqmWorzO0TTzVjYiifwoFtel8luFeXuPThQnEm5ztN4g0fnvcp7AnUPPzS/Depf17wQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -755,8 +744,8 @@ packages: dev: true optional: true - /esbuild-sunos-64/0.15.10: - resolution: {integrity: sha512-mf7hBL9Uo2gcy2r3rUFMjVpTaGpFJJE5QTDDqUFf1632FxteYANffDZmKbqX0PfeQ2XjUDE604IcE7OJeoHiyg==} + /esbuild-sunos-64/0.15.11: + resolution: {integrity: sha512-EuBdTGlsMTjEl1sQnBX2jfygy7iR6CKfvOzi+gEOfhDqbHXsmY1dcpbVtcwHAg9/2yUZSfMJHMAgf1z8M4yyyw==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -764,8 +753,8 @@ packages: dev: true optional: true - /esbuild-windows-32/0.15.10: - resolution: {integrity: sha512-ttFVo+Cg8b5+qHmZHbEc8Vl17kCleHhLzgT8X04y8zudEApo0PxPg9Mz8Z2cKH1bCYlve1XL8LkyXGFjtUYeGg==} + /esbuild-windows-32/0.15.11: + resolution: {integrity: sha512-O0/Wo1Wk6dc0rZSxkvGpmTNIycEznHmkObTFz2VHBhjPsO4ZpCgfGxNkCpz4AdAIeMczpTXt/8d5vdJNKEGC+Q==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -773,8 +762,8 @@ packages: dev: true optional: true - /esbuild-windows-64/0.15.10: - resolution: {integrity: sha512-2H0gdsyHi5x+8lbng3hLbxDWR7mKHWh5BXZGKVG830KUmXOOWFE2YKJ4tHRkejRduOGDrBvHBriYsGtmTv3ntA==} + /esbuild-windows-64/0.15.11: + resolution: {integrity: sha512-x977Q4HhNjnHx00b4XLAnTtj5vfbdEvkxaQwC1Zh5AN8g5EX+izgZ6e5QgqJgpzyRNJqh4hkgIJF1pyy1be0mQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -782,8 +771,8 @@ packages: dev: true optional: true - /esbuild-windows-arm64/0.15.10: - resolution: {integrity: sha512-S+th4F+F8VLsHLR0zrUcG+Et4hx0RKgK1eyHc08kztmLOES8BWwMiaGdoW9hiXuzznXQ0I/Fg904MNbr11Nktw==} + /esbuild-windows-arm64/0.15.11: + resolution: {integrity: sha512-VwUHFACuBahrvntdcMKZteUZ9HaYrBRODoKe4tIWxguQRvvYoYb7iu5LrcRS/FQx8KPZNaa72zuqwVtHeXsITw==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -791,34 +780,34 @@ packages: dev: true optional: true - /esbuild/0.15.10: - resolution: {integrity: sha512-N7wBhfJ/E5fzn/SpNgX+oW2RLRjwaL8Y0ezqNqhjD6w0H2p0rDuEz2FKZqpqLnO8DCaWumKe8dsC/ljvVSSxng==} + /esbuild/0.15.11: + resolution: {integrity: sha512-OgHGuhlfZ//mToxjte1D5iiiQgWfJ2GByVMwEC/IuoXsBGkuyK1+KrjYu0laSpnN/L1UmLUCv0s25vObdc1bVg==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.15.10 - '@esbuild/linux-loong64': 0.15.10 - esbuild-android-64: 0.15.10 - esbuild-android-arm64: 0.15.10 - esbuild-darwin-64: 0.15.10 - esbuild-darwin-arm64: 0.15.10 - esbuild-freebsd-64: 0.15.10 - esbuild-freebsd-arm64: 0.15.10 - esbuild-linux-32: 0.15.10 - esbuild-linux-64: 0.15.10 - esbuild-linux-arm: 0.15.10 - esbuild-linux-arm64: 0.15.10 - esbuild-linux-mips64le: 0.15.10 - esbuild-linux-ppc64le: 0.15.10 - esbuild-linux-riscv64: 0.15.10 - esbuild-linux-s390x: 0.15.10 - esbuild-netbsd-64: 0.15.10 - esbuild-openbsd-64: 0.15.10 - esbuild-sunos-64: 0.15.10 - esbuild-windows-32: 0.15.10 - esbuild-windows-64: 0.15.10 - esbuild-windows-arm64: 0.15.10 + '@esbuild/android-arm': 0.15.11 + '@esbuild/linux-loong64': 0.15.11 + esbuild-android-64: 0.15.11 + esbuild-android-arm64: 0.15.11 + esbuild-darwin-64: 0.15.11 + esbuild-darwin-arm64: 0.15.11 + esbuild-freebsd-64: 0.15.11 + esbuild-freebsd-arm64: 0.15.11 + esbuild-linux-32: 0.15.11 + esbuild-linux-64: 0.15.11 + esbuild-linux-arm: 0.15.11 + esbuild-linux-arm64: 0.15.11 + esbuild-linux-mips64le: 0.15.11 + esbuild-linux-ppc64le: 0.15.11 + esbuild-linux-riscv64: 0.15.11 + esbuild-linux-s390x: 0.15.11 + esbuild-netbsd-64: 0.15.11 + esbuild-openbsd-64: 0.15.11 + esbuild-sunos-64: 0.15.11 + esbuild-windows-32: 0.15.11 + esbuild-windows-64: 0.15.11 + esbuild-windows-arm64: 0.15.11 dev: true /escalade/3.1.1: @@ -826,10 +815,6 @@ packages: engines: {node: '>=6'} dev: true - /estree-walker/1.0.1: - resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} - dev: true - /estree-walker/2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} dev: true @@ -851,14 +836,6 @@ packages: reusify: 1.0.4 dev: true - /fetch-blob/3.2.0: - resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} - engines: {node: ^12.20 || >= 14.13} - dependencies: - node-domexception: 1.0.0 - web-streams-polyfill: 3.2.1 - dev: true - /fill-range/7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -866,13 +843,6 @@ packages: to-regex-range: 5.0.1 dev: true - /formdata-polyfill/4.0.10: - resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} - engines: {node: '>=12.20.0'} - dependencies: - fetch-blob: 3.2.0 - dev: true - /fraction.js/4.2.0: resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} dev: true @@ -926,6 +896,17 @@ packages: path-is-absolute: 1.0.1 dev: true + /glob/8.0.3: + resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.0 + once: 1.4.0 + dev: true + /globalyzer/0.1.0: resolution: {integrity: sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==} dev: true @@ -1037,16 +1018,16 @@ packages: engines: {node: '>=6'} dev: true - /leaflet.markercluster/[email protected]: + /leaflet.markercluster/[email protected]: resolution: {integrity: sha512-vPTw/Bndq7eQHjLBVlWpnGeLa3t+3zGiuM7fJwCkiMFq+nmRuG3RI3f7f4N4TDX7T4NpbAXpR2+NTRSEGfCSeA==} peerDependencies: leaflet: ^1.3.1 dependencies: - leaflet: 1.9.1 + leaflet: 1.9.2 dev: true - /leaflet/1.9.1: - resolution: {integrity: sha512-5FcDAMTLAuOq0RisQQbTFatl8Sl4bM0gMrBfuHbl7CYbYJv45BfCS8oU3T644MLjleBq1Ncq541QEA1pqUGTFA==} + /leaflet/1.9.2: + resolution: {integrity: sha512-Kc77HQvWO+y9y2oIs3dn5h5sy2kr3j41ewdqCMEUA4N89lgfUUfOBy7wnnHEstDpefiGFObq12FdopGRMx4J7g==} dev: true /lilconfig/2.0.6: @@ -1064,8 +1045,8 @@ packages: sourcemap-codec: 1.4.8 dev: true - /magic-string/0.26.5: - resolution: {integrity: sha512-yXUIYOOQnEHKHOftp5shMWpB9ImfgfDJpapa38j/qMtTj5QHWucvxP4lUtuRmHT9vAzvtpHkWKXW9xBwimXeNg==} + /magic-string/0.26.7: + resolution: {integrity: sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==} engines: {node: '>=12'} dependencies: sourcemap-codec: 1.4.8 @@ -1117,15 +1098,22 @@ packages: brace-expansion: 1.1.11 dev: true - /minimist/1.2.6: - resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} + /minimatch/5.1.0: + resolution: {integrity: sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /minimist/1.2.7: + resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==} dev: true /mkdirp/0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true dependencies: - minimist: 1.2.6 + minimist: 1.2.7 dev: true /mri/1.2.0: @@ -1152,20 +1140,6 @@ packages: resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} dev: true - /node-domexception/1.0.0: - resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} - engines: {node: '>=10.5.0'} - dev: true - - /node-fetch/3.2.10: - resolution: {integrity: sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - data-uri-to-buffer: 4.0.0 - fetch-blob: 3.2.0 - formdata-polyfill: 4.0.10 - dev: true - /node-gyp-build/4.5.0: resolution: {integrity: sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg==} hasBin: true @@ -1223,29 +1197,29 @@ packages: engines: {node: '>=0.10.0'} dev: true - /postcss-import/[email protected]: + /postcss-import/[email protected]: resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} peerDependencies: postcss: ^8.0.0 dependencies: - postcss: 8.4.17 + postcss: 8.4.18 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.1 dev: true - /postcss-js/[email protected]: + /postcss-js/[email protected]: resolution: {integrity: sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ==} engines: {node: ^12 || ^14 || >= 16} peerDependencies: postcss: ^8.3.3 dependencies: camelcase-css: 2.0.1 - postcss: 8.4.17 + postcss: 8.4.18 dev: true - /postcss-load-config/[email protected]: + /postcss-load-config/[email protected]: resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} engines: {node: '>= 10'} peerDependencies: @@ -1258,17 +1232,17 @@ packages: optional: true dependencies: lilconfig: 2.0.6 - postcss: 8.4.17 + postcss: 8.4.18 yaml: 1.10.2 dev: true - /postcss-nested/[email protected]: + /postcss-nested/[email protected]: resolution: {integrity: sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==} engines: {node: '>=12.0'} peerDependencies: postcss: ^8.2.14 dependencies: - postcss: 8.4.17 + postcss: 8.4.18 postcss-selector-parser: 6.0.10 dev: true @@ -1284,8 +1258,8 @@ packages: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: true - /postcss/8.4.17: - resolution: {integrity: sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==} + /postcss/8.4.18: + resolution: {integrity: sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.4 @@ -1293,14 +1267,14 @@ packages: source-map-js: 1.0.2 dev: true - /prettier-plugin-svelte/2.7.1_nk6d2fkgcllkkdynqbuearcure: - resolution: {integrity: sha512-H33qjhCBZyd9Zr1A5hUAYDh7j0Mf97uvy7XcA7CP4nNSYrNcPvBUf7wI8K9NptWTIs0S41QtgTWmJIUiGlEBtw==} + /prettier-plugin-svelte/2.8.0_lrllcp5xtrkmmdzifit4hd52ze: + resolution: {integrity: sha512-QlXv/U3bUszks3XYDPsk1fsaQC+fo2lshwKbcbO+lrSVdJ+40mB1BfL8OCAk1W9y4pJxpqO/4gqm6NtF3zNGCw==} peerDependencies: prettier: ^1.16.4 || ^2.0.0 svelte: ^3.2.0 dependencies: prettier: 2.7.1 - svelte: 3.50.1 + svelte: 3.52.0 dev: true /prettier/2.7.1: @@ -1486,7 +1460,7 @@ packages: hasBin: true dependencies: buffer-crc32: 0.2.13 - minimist: 1.2.6 + minimist: 1.2.7 sander: 0.5.1 sourcemap-codec: 1.4.8 dev: true @@ -1523,24 +1497,24 @@ packages: engines: {node: '>= 0.4'} dev: true - /svelte-hmr/[email protected]: + /svelte-hmr/[email protected]: resolution: {integrity: sha512-Aw21SsyoohyVn4yiKXWPNCSW2DQNH/76kvUnE9kpt4h9hcg9tfyQc6xshx9hzgMfGF0kVx0EGD8oBMWSnATeOg==} engines: {node: ^12.20 || ^14.13.1 || >= 16} peerDependencies: svelte: '>=3.19.0' dependencies: - svelte: 3.50.1 + svelte: 3.52.0 dev: true - /svelte-outclick/3.1.0: - resolution: {integrity: sha512-1p/4UAuBfgfltMByTXjE3wbNAYaPihsB9HkhYrKgkiUgZSUtQ+Tpr/gxLu0LIsgft1EKt1izWg/VfzO+f5lQUA==} + /svelte-outclick/3.2.0: + resolution: {integrity: sha512-mQ8myBEnB4GoVlwSkQ6tmTtLKHz2ZmZJ9WKgolEGjKf6DzbO3eqkFZIZgzLFU1PR/GQngdPativJzrL3/oeQ+Q==} dev: true /svelte-pincode/2.1.2: resolution: {integrity: sha512-TlW0PScUjPR3Abaptcgw0gP+52F+1nlDlXy8zvnO9DVgkESm9kvwFY4RRj0po5aDy/Tp0docNiQmoaLKVTLMiw==} dev: true - /svelte-preprocess/4.10.7_r763xggw3my2ousygwguxmp6ru: + /svelte-preprocess/4.10.7_vidagnlh55bt6yxji7dk5y5wla: resolution: {integrity: sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==} engines: {node: '>= 9.11.2'} requiresBuild: true @@ -1585,28 +1559,28 @@ packages: '@types/sass': 1.43.1 detect-indent: 6.1.0 magic-string: 0.25.9 - postcss: 8.4.17 + postcss: 8.4.18 sorcery: 0.10.0 strip-indent: 3.0.0 - svelte: 3.50.1 + svelte: 3.52.0 dev: true - /svelte/3.50.1: - resolution: {integrity: sha512-bS4odcsdj5D5jEg6riZuMg5NKelzPtmsCbD9RG+8umU03TeNkdWnP6pqbCm0s8UQNBkqk29w/Bdubn3C+HWSwA==} + /svelte/3.52.0: + resolution: {integrity: sha512-FxcnEUOAVfr10vDU5dVgJN19IvqeHQCS1zfe8vayTfis9A2t5Fhx+JDe5uv/C3j//bB1umpLJ6quhgs9xyUbCQ==} engines: {node: '>= 8'} dev: true - /sveltekit-i18n/[email protected]: + /sveltekit-i18n/[email protected]: resolution: {integrity: sha512-6eygICleGCSL7elY7A3trF8XUhV+mlW56ZSoD0UUKXlw+Y6u0MTTHDq48u1LyY73SfnlbPHXgTarhTjZ0BvUKA==} peerDependencies: svelte: ^3.x dependencies: - '@sveltekit-i18n/base': [email protected] + '@sveltekit-i18n/base': [email protected] '@sveltekit-i18n/parser-default': 1.0.3 - svelte: 3.50.1 + svelte: 3.52.0 dev: true - /tailwindcss/[email protected]: + /tailwindcss/[email protected]: resolution: {integrity: sha512-YSneUCZSFDYMwk+TGq8qYFdCA3yfBRdBlS7txSq0LUmzyeqRe3a8fBQzbz9M3WS/iFT4BNf/nmw9mEzrnSaC0g==} engines: {node: '>=12.13.0'} hasBin: true @@ -1626,11 +1600,11 @@ packages: normalize-path: 3.0.0 object-hash: 3.0.0 picocolors: 1.0.0 - postcss: 8.4.17 - postcss-import: [email protected] - postcss-js: [email protected] - postcss-load-config: [email protected] - postcss-nested: [email protected] + postcss: 8.4.18 + postcss-import: [email protected] + postcss-js: [email protected] + postcss-load-config: [email protected] + postcss-nested: [email protected] postcss-selector-parser: 6.0.10 postcss-value-parser: 4.2.0 quick-lru: 5.1.1 @@ -1669,8 +1643,8 @@ packages: busboy: 1.6.0 dev: true - /update-browserslist-db/[email protected]: - resolution: {integrity: sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==} + /update-browserslist-db/[email protected]: + resolution: {integrity: sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -1690,8 +1664,8 @@ packages: safe-buffer: 5.2.1 dev: true - /vite/3.1.4: - resolution: {integrity: sha512-JoQI08aBjY9lycL7jcEq4p9o1xUjq5aRvdH4KWaXtkSx7e7RpAh9D3IjzDWRD4Fg44LS3oDAIOG/Kq1L+82psA==} + /vite/3.1.8: + resolution: {integrity: sha512-m7jJe3nufUbuOfotkntGFupinL/fmuTNuQmiVE7cH2IZMuf4UbfbGYMUT3jVWgGYuRVLY9j8NnrRqgw5rr5QTg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -1709,19 +1683,14 @@ packages: terser: optional: true dependencies: - esbuild: 0.15.10 - postcss: 8.4.17 + esbuild: 0.15.11 + postcss: 8.4.18 resolve: 1.22.1 rollup: 2.78.1 optionalDependencies: fsevents: 2.3.2 dev: true - /web-streams-polyfill/3.2.1: - resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} - engines: {node: '>= 8'} - dev: true - /wif/2.0.6: resolution: {integrity: sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==} dependencies: diff --git a/src/app.html b/src/app.html index d8545747..57ffbcce 100644 --- a/src/app.html +++ b/src/app.html @@ -4,7 +4,8 @@ <meta charset="utf-8" /> <link rel="icon" href="%sveltekit.assets%/favicon.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> - <meta name="description" content="Easy to use bitcoin point of sale app and web wallet." /> + <meta name="description" content="An easy bitcoin web wallet" /> + <meta name="keywords" content="open source bitcoin lightning web wallet" /> <meta property="og:image" content="/icons/logo.svg" /> <title>coinos</title> %sveltekit.head% diff --git a/src/components/AppHeader.svelte b/src/components/AppHeader.svelte index f9413efb..302a9c7a 100644 --- a/src/components/AppHeader.svelte +++ b/src/components/AppHeader.svelte @@ -7,7 +7,6 @@ import { page } from '$app/stores'; export let data; - export let avatarPosition = 'left-[calc(50vw-64px)] md:left-[calc(15vw-48px)] bottom-[-64px]'; let { subject, user } = data; $: update(data); @@ -35,7 +34,7 @@ $tempProfileFiles && $tempProfileFiles.banner ? `url(${$tempProfileFiles.banner})` : subject?.banner - ? `url(/api/public/${subject.username}-banner.png)` + ? `url(/api/public/${subject.username}-banner.webp)` : undefined; </script> diff --git a/src/components/Avatar.svelte b/src/components/Avatar.svelte index ed1a499a..c7339bf5 100644 --- a/src/components/Avatar.svelte +++ b/src/components/Avatar.svelte @@ -2,7 +2,6 @@ import { colorTheme, tempProfileFiles } from '$lib/store'; import { Icon } from '$comp'; - export let avatarPosition; export let user; </script> @@ -14,7 +13,7 @@ <img src={$tempProfileFiles && $tempProfileFiles.profile ? $tempProfileFiles.profile - : `/api/public/${user.username}-profile.png`} + : `/api/public/${user.username}-profile.webp`} class="w-full h-full object-cover object-center overflow-hidden" alt={user.username} /> diff --git a/src/components/Calendar.svelte b/src/components/Calendar.svelte new file mode 100644 index 00000000..7504f48e --- /dev/null +++ b/src/components/Calendar.svelte @@ -0,0 +1,14 @@ +<svg + xmlns="http://www.w3.org/2000/svg" + class="absolute bottom-0 left-0 -mb-0.5 transform translate-x-1/2 -translate-y-1/2 text-black peer-placeholder-shown:text-gray-300 h-6 w-6" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" +> + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" + /> +</svg> diff --git a/src/components/Card.svelte b/src/components/Card.svelte new file mode 100644 index 00000000..44ab635f --- /dev/null +++ b/src/components/Card.svelte @@ -0,0 +1,14 @@ +<svg + xmlns="http://www.w3.org/2000/svg" + class="absolute bottom-0 left-0 -mb-0.5 transform translate-x-1/2 -translate-y-1/2 text-black peer-placeholder-shown:text-gray-300 h-6 w-6" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" +> + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" + /> +</svg> diff --git a/src/components/Lock.svelte b/src/components/Lock.svelte new file mode 100644 index 00000000..11475acf --- /dev/null +++ b/src/components/Lock.svelte @@ -0,0 +1,14 @@ +<svg + xmlns="http://www.w3.org/2000/svg" + class="absolute bottom-0 left-0 -mb-0.5 transform translate-x-1/2 -translate-y-1/2 text-black peer-placeholder-shown:text-gray-300 h-6 w-6" + fill="none" + viewBox="0 0 24 24" + stroke="currentColor" +> + <path + stroke-linecap="round" + stroke-linejoin="round" + stroke-width="2" + d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" + /> +</svg> diff --git a/src/components/Pin.svelte b/src/components/Pin.svelte new file mode 100644 index 00000000..8f33a7f3 --- /dev/null +++ b/src/components/Pin.svelte @@ -0,0 +1,35 @@ +<script> + import { tick } from 'svelte'; + import { pin } from '$lib/store'; + import { Pincode, PincodeInput } from 'svelte-pincode'; + let pinCode = []; + let pinInput; + + $: pinInput && tick().then(pinInput && pinInput.focusFirstInput); + $: pinCode.length > 5 && ($pin = pinCode.join('')); +</script> + +<div class="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-20"> + <div class="relative top-1/3 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white space-y-5"> + <h1 class="text-center text-2xl font-semibold">Please enter your PIN</h1> + <div class="text-center"> + <Pincode bind:code={pinCode} bind:this={pinInput}> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + </Pincode> + </div> + + <div class="w-full flex"> + <button + type="submit" + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80 mx-auto" + > + <div class="my-auto">Cancel</div> + </button> + </div> + </div> +</div> diff --git a/src/components/Qr.svelte b/src/components/Qr.svelte index 0ae028b9..3870abe0 100644 --- a/src/components/Qr.svelte +++ b/src/components/Qr.svelte @@ -57,8 +57,8 @@ {#if mask} <div class="absolute bg-white w-[300px] h-[300px] t-0 l-0 z-10" out:fade /> {/if} -<div bind:this={qr} on:click={toggle} class:hidden={full} class="relative overflow-visible" /> -<div bind:this={fullQr} on:click={toggle} class:hidden={!full} /> +<div bind:this={qr} on:click={toggle} on:keydown={toggle} class:hidden={full} class="relative overflow-visible" /> +<div bind:this={fullQr} on:click={toggle} on:keydown={toggle} class:hidden={!full} /> <style> div { diff --git a/src/components/RegisterOrLogin.svelte b/src/components/RegisterOrLogin.svelte index 0b954387..bf2d2084 100644 --- a/src/components/RegisterOrLogin.svelte +++ b/src/components/RegisterOrLogin.svelte @@ -1,18 +1,28 @@ <script> + import { tick } from 'svelte'; + import { Pincode, PincodeInput } from 'svelte-pincode'; import { fly } from 'svelte/transition'; import { enhance } from '$app/forms'; import { Icon } from '$comp'; import { focus } from '$lib/utils'; - import { redirect } from '$lib/store'; + import { loginRedirect } from '$lib/store'; import { t } from '$lib/translations'; + let token, tokenInput; + let code = []; + + $: need2fa = form?.message === '2fa'; + $: if (need2fa) tokenInput && tick().then(tokenInput.focusFirstInput); + export let form; export let pageID; - let username, - password, - email, - twofa = ''; + let username, password, email, submit; + + $: update(form); + let update = (form) => form && ({ username, password } = form); + + $: token && token?.length === 6 && tick().then(() => submit.click()); let revealPassword = false; @@ -31,101 +41,122 @@ <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]"> - <h1 class="text-2xl font-bold text-center">{$t('login.' + pageID)}</h1> + {#if need2fa} + <h1 class="text-2xl font-bold text-center">Enter 2FA Code</h1> - {#if form?.error} - <div class="text-red-600 text-center" in:fly> - {form.error} - </div> - {/if} + {#if form?.token} + <div class="text-red-600 text-center" in:fly>Invalid token, try again</div> + {/if} + + <form class="space-y-5" {action} method="POST" use:enhance> + <input type="hidden" name="username" value={username} /> + <input type="hidden" name="password" value={password} /> + <input type="hidden" name="token" value={token} /> + + <div> + <Pincode bind:code bind:value={token} bind:this={tokenInput}> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + </Pincode> + </div> + <button + bind:this={submit} + type="submit" + class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" + >{$t('login.' + pageID)} + </button> + </form> + {:else} + <h1 class="text-2xl font-bold text-center">{$t('login.' + pageID)}</h1> - <form class="space-y-5" {action} method="POST" use:enhance> - {#if $redirect} - <input type="hidden" name="redirect" value={$redirect} /> + {#if form?.error} + <div class="text-red-600 text-center" in:fly> + {form.error} + </div> {/if} - <div> - <label for="username" class="font-semibold">{$t('login.username')}</label> - <input - name="username" - type="text" - required - class="bg-primary" - bind:value={username} - use:focus - autocapitalize="none" - /> - </div> + <form class="space-y-5" {action} method="POST" use:enhance> + {#if $loginRedirect} + <input type="hidden" name="loginRedirect" value={$loginRedirect} /> + {/if} - <div class="relative"> - <label for="password" class="block font-semibold">{$t('login.password')}</label> - {#if revealPassword} + <div> + <label for="username" class="font-semibold">{$t('login.username')}</label> <input - name="password" + name="username" type="text" required class="bg-primary" - bind:value={password} + bind:value={username} + use:focus autocapitalize="none" /> - {:else} - <input - name="password" - type="password" - required - class="bg-primary" - bind:value={password} - autocapitalize="none" - /> - {/if} - <button - type="button" - on:click={() => (revealPassword = !revealPassword)} - class="absolute right-5 top-10" - > - <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> - </button> - </div> + </div> - {#if pageID === 'signIn'} - <div> - <label for="token" class="font-semibold" - >{$t('login.token')} - <span class="text-secondary">({$t('login.optional')})</span></label + <div class="relative"> + <label for="password" class="block font-semibold">{$t('login.password')}</label> + {#if revealPassword} + <input + name="password" + type="text" + required + class="bg-primary" + bind:value={password} + autocapitalize="none" + /> + {:else} + <input + name="password" + type="password" + required + class="bg-primary" + bind:value={password} + autocapitalize="none" + /> + {/if} + <button + type="button" + on:click={() => (revealPassword = !revealPassword)} + class="absolute right-5 top-10" > - <input name="token" type="text" class="bg-primary" bind:value={twofa} /> + <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> + </button> </div> - {/if} - {#if pageID === 'register'} - <p class="text-secondary text-sm"> - {$t('login.passwordRecommendation')} - </p> - {:else} - <!-- TODO --> - <!-- <div class="flex justify-end items-center"> --> - <!-- <a href="/forgot" class="underline underline-offset-4 text-black text-sm" --> - <!-- >{$t('login.forgotUserOrPassword')}</a --> - <!-- > --> - <!-- </div> --> - {/if} + {#if pageID === 'register'} + <p class="text-secondary text-sm"> + {$t('login.passwordRecommendation')} + </p> + {:else} + <!-- TODO --> + <!-- <div class="flex justify-end items-center"> --> + <!-- <a href="/forgot" class="underline underline-offset-4 text-black text-sm" --> + <!-- >{$t('login.forgotUserOrPassword')}</a --> + <!-- > --> + <!-- </div> --> + {/if} - <button - type="submit" - class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" - >{$t('login.' + pageID)} - </button> - </form> + <button + type="submit" + class="bg-black text-white w-full rounded-2xl p-4 font-semibold hover:opacity-80" + >{$t('login.' + pageID)} + </button> + </form> - <p class="text-secondary text-center font-medium"> - {pageID === 'register' ? $t('login.haveAccount') : $t('login.noAccount')} - <a - href={pageID === 'register' ? '/login' : '/register'} - class="block md:inline text-black underline underline-offset-4 hover:opacity-80" - > - {pageID === 'register' ? $t('login.signIn') : $t('login.register')} - </a> - </p> + <p class="text-secondary text-center font-medium"> + {pageID === 'register' ? $t('login.haveAccount') : $t('login.noAccount')} + <a + href={pageID === 'register' ? '/login' : '/register'} + class="block md:inline text-black underline underline-offset-4 hover:opacity-80" + > + {pageID === 'register' ? $t('login.signIn') : $t('login.register')} + </a> + </p> + {/if} </div> </div> </div> diff --git a/src/components/Tip.svelte b/src/components/Tip.svelte index 25dbe3b9..6af1e00a 100644 --- a/src/components/Tip.svelte +++ b/src/components/Tip.svelte @@ -188,8 +188,4 @@ background: black; cursor: pointer; } - - #slider.slider-track::-moz-range-progress { - background: black; - } </style> diff --git a/src/components/index.js b/src/components/index.js index 08ec217a..c3a5000c 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,3 +1,7 @@ +export { default as Pin } from './Pin.svelte'; +export { default as Card } from './Card.svelte'; +export { default as Lock } from './Lock.svelte'; +export { default as Calendar } from './Calendar.svelte'; export { default as Avatar } from './Avatar.svelte'; export { default as Left } from './Left.svelte'; export { default as Numpad } from './Numpad.svelte'; diff --git a/src/hooks.server.js b/src/hooks.server.js index 58882771..a5b1eef3 100644 --- a/src/hooks.server.js +++ b/src/hooks.server.js @@ -2,9 +2,8 @@ import { auth, get } from '$lib/utils'; export async function handle({ event, resolve }) { let token = event.cookies.get('token'); - if (token) { - event.request.headers.set('authorization', `Bearer ${token}`); + if (token) { try { event.locals.user = await get('/me', auth(event.cookies)); } catch (e) { @@ -13,7 +12,6 @@ export async function handle({ event, resolve }) { } const response = await resolve(event); - response.headers.set('x-custom-header', 'potato'); return response; } diff --git a/src/lib/socket.js b/src/lib/socket.js index 02d73a48..b385db9d 100644 --- a/src/lib/socket.js +++ b/src/lib/socket.js @@ -1,9 +1,10 @@ import { get } from 'svelte/store'; -import { invoices, newPayment, rate } from '$lib/store'; +import { invoices, newPayment, rate, user } from '$lib/store'; import { success } from '$lib/utils'; import { env } from '$env/dynamic/public'; const socketUrl = env.PUBLIC_SOCKET; +const btc = env.PUBLIC_BTC; let interval, socket, token; @@ -18,12 +19,14 @@ export const messages = (data) => ({ rate.set(data); }, - payment() { + async payment() { let { amount, invoice } = data; - console.log('INVOICE', invoice); + if (get(user).account_id !== data.account_id) return; + if (invoice) { invoices.set({ ...get(invoices), [invoice.uuid]: invoice }); } + newPayment.set(true); if (amount > 0) { success(`Received ${amount} sats!`); diff --git a/src/lib/store.js b/src/lib/store.js index c64ad39f..3174a86c 100644 --- a/src/lib/store.js +++ b/src/lib/store.js @@ -1,20 +1,24 @@ import { get, writable } from 'svelte/store'; import { browser } from '$app/environment'; -// creates a Svelte store synced to sessionStorage with the given key and default value -const persistSession = (key, defaultValue) => { +const persistSession = (key, defaultValue, expiry) => { + if (expiry && browser && new Date() - expiry > sessionStorage.getItem(key + 'time')) { + sessionStorage.removeItem(key); + } + let s = writable( browser && sessionStorage.getItem(key) && sessionStorage.getItem(key) !== 'undefined' ? JSON.parse(sessionStorage.getItem(key)) : defaultValue ); + expiry && browser && sessionStorage.setItem(key + 'time', Date.now()); + s.subscribe((v) => browser && sessionStorage.setItem(key, JSON.stringify(v))); return s; }; -// creates a Svelte store synced to localStorage with the given key and default value const persistLocal = (key, defaultValue) => { let s = writable( browser && localStorage.getItem(key) && localStorage.getItem(key) !== 'undefined' @@ -27,8 +31,9 @@ const persistLocal = (key, defaultValue) => { return s; }; -export const redirect = writable(); +export const loginRedirect = writable(); export const rate = writable(); +export const user = writable(); export const selectedRate = writable(); export const token = persistSession('token'); export const invoices = writable({}); @@ -37,3 +42,4 @@ export const colorTheme = writable('from-primary to-gradient'); export const tempProfileFiles = writable(); export const avatarUpload = writable(); export const bannerUpload = writable(); +export const pin = persistSession('pin', undefined, 5 * 60 * 1000); diff --git a/src/lib/ticket.js b/src/lib/ticket.js new file mode 100644 index 00000000..708c10f4 --- /dev/null +++ b/src/lib/ticket.js @@ -0,0 +1,31 @@ +import { auth, get, post } from '$lib/utils'; +import { env } from '$env/dynamic/private'; +import tickets from '$lib/tickets'; + +export const createTicket = async (cookies, username) => { + let { ticket } = await get('/ticket'); + + let { address } = await post( + '/invoice', + { invoice: { network: 'liquid' }, user: { username } }, + auth(cookies) + ); + + let { asset } = await post( + '/assets', + { + address, + name: `Launch Party Ticket ${ticket}`, + ticker: 'LPT', + precision: 0, + asset_amount: '1', + token_amount: 0, + domain: 'coinos.io', + filename: tickets[ticket] + }, + { authorization: `Bearer ${env.LAUNCH}` } + ); + + await post('/ticket', { asset }, { authorization: `Bearer ${env.LAUNCH}` }); + return asset; +}; diff --git a/src/lib/tickets.js b/src/lib/tickets.js new file mode 100644 index 00000000..b0ca0ac0 --- /dev/null +++ b/src/lib/tickets.js @@ -0,0 +1,52 @@ +export default [ + 'QmPbFkiXNuaQyfwD4W9GcXP7qWaNd6wFd9KCnJir5mes1J', + 'QmPVjvbaMxSJRpDxMLYTdUWQbknej1vYLckBQ2pAgxZgJN', + 'QmVasycSFzCYhyXrcvTE7mN3d6W78RFnrhzg5cRF6Dp6Zq', + 'QmTmnhjpTBjZdeHwXgT9Dftc3uWMW73qsZpZ5ECrTyiKKZ', + 'QmWDBiyVPyFkzUrEdvy7DSJe1rBQpene1m6quKAtyjBP6a', + 'QmcxxzQZwgG7SE9QbHNvoTiCi6Dhs9rrz9roqvxm2JQA88', + 'Qmd9ThEpXLLHimEzRUWEFrAGLSw5PAHzn2SmwfCnjNLwWt', + 'Qmc7pWw1yxc2ARBWitDMYiyPkWK61NsXXU8oYAQwNAvcx1', + 'QmX9Hgw1rMFUgBBuq8QfZZDzLjN5xCED19fwFdDeDDqwmr', + 'QmconCAyNyaagqKvvfKCbG7Eu8c6vCtkv2ivvxMoFN9YwA', + 'QmWbFmvViy5gx4JdD5sw5YJETet4ZQcXtYgijYnrsnSZ3a', + 'QmP6QiiJwoqkduZMqdd5e5htHZ4aYHCsvEu7WEEUknKYa5', + 'QmefHk8hvBUHsuKugntok5u75Q8i886cnPwsj4LZZB6uWg', + 'QmdrSDK5ByYHxc88hfDu8hQgK5XYFpXSfPorUXdyTbE437', + 'QmZ6ZycjRzYpsjnLm1Lfgk1fXXDdhcYRkyfmqYJZVGwADy', + 'QmSkBxS5yLPjB2VPs89oQRZANsoyodK6s8s52E8GKXr2zt', + 'QmSn8TCPwq3UZ1Z4eyko76CvQo2H2Hyef1NGboDQN22m5i', + 'QmP6CTH5zrKMzZwtjy7VaK9NXiq4Gw53kmuRjUbJjeovoe', + 'QmdwT4yvz9Xf8g3t6irKRoh3Wa7MwRQrcUJckQxyQe1Bc5', + 'QmcwuHH3bD5JExqjDvKqRQ3V5xEVGqs94qPNVDzaCqJa5a', + 'QmRfVduHwn8sXwSoyfgZpxSi8L7EjP4LMp6fmijo9VApDp', + 'Qmc4TGj1Ahbg9HJ9WDXDm2bXsNkT4zRDp8Wdw9wqqdEwuH', + 'QmY8uQiHMWA8ztf2wAurNFW7eiTHQEUiLTQ2vGceMwVqz3', + 'QmXiXbaD12F4mkbUSQcKhWshkPU4oi29cUawZGnPk86VVU', + 'QmZaiAGjvBZBw4suz33s9JQNzY2Kshean2mQQPCa6gKMQe', + 'Qme8bDSdzX7tanPYf672xXW8vPdkLkeSLqLzHJqidRXsYJ', + 'QmRJirjuUKgh2AMyBF4jNquaXHFPBtDJf3gKFC614hX7qu', + 'QmdRoSKSgabcrpekNBPB6T47RBtJnCETYE8ry2a3P37MUe', + 'QmXoqArruHSghrUGW35vVQHPknBG5Cj939iWWHHXU3B616', + 'QmYCxtDuoHLMAFhNpifUsLU9ptAeFKwyxSiSEJhDQLMZtN', + 'QmQZQBw8wgzUsrmVDEejdePK23AEJbk9aUracvxLPyHz2x', + 'QmSsbu7U3i62AQKuYqfisRo5E6nqdMZfHRKWYJgouaXDxD', + 'QmaP4adVQuWyqtN4QCXeYXj4nr19Je4EqsaTG1zM9ywqFS', + 'QmPPsvgfXsiN3wNqdAwHAhQVpw6SZWNpry9Na6R2Xb7zXY', + 'QmR7z6wQmcfZUvRsVK83GRRboyvwZEDQujLwumMY33nAit', + 'QmQXgkgorDLPUyQpFbW8aLUW6jioQjUKamtXw5UuNCttDg', + 'QmcwYBeXt1BPfdSrtSrXFpWctRZ7RDHvmKTttC5hPPTSzG', + 'QmTUexZnw3xPy3YVdUsqJALwTvy2qFax5b3QpJVMvTCJQb', + 'QmNLkYXFUvctpfeeDTZxruZE2f834EUnYMM6JJUeTcUB4J', + 'QmT7544UaTPgKFYMCpPE4unXphNGX71BLZBPKLkxAijSmj', + 'QmTpVR4dqX1QV4fae5pZ1ACtRcANHwEWCLwpMXFmwE95Sm', + 'Qmdfyps5fW8d8wstXMfpvpei2DTmaADBWgmxLT7e7uan4P', + 'QmUSK8byipYoG3H97QpmPrHw9vYrvHygyhkiM81XmaKyxS', + 'QmQUGRvp96LTw5i8Ly9f5LoMvUpeQHCLGNqC88KV74wEHK', + 'Qma693TFLh9Z1MeBFzhPHi6Yk4tmTcZY2jogwJ8cPkU2B4', + 'QmNu8FyMpmkFdbCXGRVG8mKJtMvvCoLP8LNzFsxBQwxQeS', + 'QmWZHE87PKujL1MGLuFgP3D12o5GQxYbvbeTRgr12f1XMu', + 'QmcZJn8gPGd3poS9yuiNJh6iP4cn9uy5fnypky71r4sSSw', + 'QmTwrAF4E9ucpdnrUZFj6EfrQamGv7Qyn7ceSx1DUQYceC', + 'QmexssxU5iTXypgNqrmrQwMDJnTNxyZUxor5wDLnwb5TZp' +]; diff --git a/src/lib/utils.js b/src/lib/utils.js index 54e965d9..7eeab648 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -9,6 +9,17 @@ export function scroll(section) { const base = browser ? '' : env.PUBLIC_COINOS_URL; +export const g = (url, fetch, headers) => + fetch(base + url, { headers }) + .then((r) => r.text()) + .then((body) => { + try { + return JSON.parse(body); + } catch (e) { + throw new Error(body); + } + }); + export const get = (url, headers = {}) => fetch(base + url, { headers }) .then((r) => r.text()) @@ -31,6 +42,8 @@ export const post = (url, body, headers) => { throw new Error(body); } + + if (body.error) throw body.error; if (body instanceof Error) throw body; if (body.name === 'Error') throw new Error(body.message); @@ -97,7 +110,17 @@ export const info = (m) => { export const login = async (user, cookies) => { let maxAge = 30 * 24 * 60 * 60; - let { token } = await post('/login', user); + let res = await fetch(base + '/login', { + method: 'POST', + body: JSON.stringify(user), + headers: { 'content-type': 'application/json', accept: 'application/json' } + }); + if (res.status === 401) { + let text = await res.text(); + if (text.startsWith('2fa')) throw new Error('2fa'); + } + + let { token } = await res.json(); if (!token) throw new Error('Login failed'); let expires = new Date(); diff --git a/src/locales/en.json b/src/locales/en.json index a6c94b58..b5aef47a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -32,7 +32,7 @@ "bio": "Strategic & Project Management. Jack of all Trades. Can always find time to meet for coffee." }, "cole": { - "name": "Cole Goodwin", + "name": "Cole", "position": "Bitcoin Professional", "bio": "As a Certified Bitcoin Professional, Cole is a driven individual with a passion for freedom. He came across bitcoin in 2016 and studied it in depth. Now he wants to introduce the lightning network to as many users as possible." }, @@ -152,7 +152,7 @@ "settings": "Settings", "support": "Support" }, - "newSiteHeader": "Welcome to the <a href=\"/news\" class=\"underline underline-offset-4 text-black hover:opacity-80\" >new and improved coinos</a>, focused on merchant adoption! Looking for the old coinos? Visit <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>.", + "newSiteHeader": "Join us at our <a href=\"/launch\" class=\"underline underline-offset-4 text-black hover:opacity-80\">launch party</a> in Vancouver on October 26! Looking for the old coinos? Visit <a href=\"https://classic.coinos.io\" class=\"underline underline-offset-4 text-black hover:opacity-80\">classic.coinos.io</a>", "news": { "header": "News", "subheader": "Latest Updates", diff --git a/src/locales/es.json b/src/locales/es.json index 5fad6dd8..068d93a5 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -32,7 +32,7 @@ "bio": "Gestión Estratégica y de Proyectos. Ella es una todóloga. Siempre encuentra tiempo para reunirse a tomar un café." }, "cole": { - "name": "Cole Goodwin", + "name": "Cole", "position": "Profesional en Bitcoin", "bio": "Como profesional certificado de Bitcoin, Cole es una persona motivada y apasionada por la libertad. Encontró bitcoin en el año 2016 bitcoin y se dedicó a estudiarlo en profundidad. Ahora quiere presentar Lightning Network a tantos usuarios como sea posible." }, diff --git a/src/routes/+layout.server.js b/src/routes/+layout.server.js index e110b467..be81e19f 100644 --- a/src/routes/+layout.server.js +++ b/src/routes/+layout.server.js @@ -1,12 +1,12 @@ import { error, redirect } from '@sveltejs/kit'; -import cookie from 'cookie'; import { protectedRoutes } from '$lib/utils'; import { get, post, auth } from '$lib/utils'; import { env } from '$env/dynamic/public'; export const { PUBLIC_BTC: btc } = env; -export async function load({ cookies, request, url, params }) { +export async function load({ cookies, locals, request, url, params }) { + url.pathname; let token = cookies.get('token'); let user; let rates; @@ -17,11 +17,7 @@ export async function load({ cookies, request, url, params }) { } if (token && token !== 'undefined') { - try { - user = await get('/me', auth(cookies)); - } catch (e) { - throw redirect(307, '/logout'); - } + user = locals.user; if (protectedRoutes.find((p) => url.pathname.match(p))) { if (user.username !== params.username) throw error(404, 'user not found'); diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 42d249f4..5cc75b30 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -3,7 +3,7 @@ import '../app.css'; import { onMount } from 'svelte'; import { connect } from '$lib/socket'; - import { selectedRate, rate } from '$lib/store'; + import { selectedRate, rate, user } from '$lib/store'; import { page } from '$app/stores'; import { browser } from '$app/environment'; import { goto } from '$app/navigation'; @@ -14,12 +14,17 @@ const localeLocalStorageKey = 'sveltekit-i18n-locale'; export let data; - let { user, rate: r, token, rates } = data; + + let r, u, token, rates; + $: update(data); - let update = () => ({ user, rate: r, token, rates } = data); - $rate = r; + let update = () => { + ({ user: u, rate: r, token, rates } = data); + $rate = r; + $user = u; + }; - $: $selectedRate = user && $rate * (rates[user.currency] / rates.USD); + $: $selectedRate = u && r * (rates[u.currency] / rates.USD); onMount(() => { let localStorageLocale = localStorage.getItem(localeLocalStorageKey); diff --git a/src/routes/[username]/+page.server.js b/src/routes/[username]/+page.server.js index d6f2978c..0840161a 100644 --- a/src/routes/[username]/+page.server.js +++ b/src/routes/[username]/+page.server.js @@ -1,4 +1,3 @@ -import cookie from 'cookie'; import { get, post } from '$lib/utils'; export async function load({ params }) { diff --git a/src/routes/[username]/dashboard/+page.server.js b/src/routes/[username]/dashboard/+page.server.js new file mode 100644 index 00000000..b6206324 --- /dev/null +++ b/src/routes/[username]/dashboard/+page.server.js @@ -0,0 +1 @@ +export const load = ({ parent }) => parent(); diff --git a/src/routes/[username]/settings/+page.server.js b/src/routes/[username]/settings/+page.server.js index 7053a939..ae7e0a78 100644 --- a/src/routes/[username]/settings/+page.server.js +++ b/src/routes/[username]/settings/+page.server.js @@ -3,9 +3,11 @@ import { auth, get, post } from '$lib/utils'; export const actions = { default: async ({ cookies, request }) => { + let form = Object.fromEntries(await request.formData()); + let user = { ...(await get('/me', auth(cookies))), - ...Object.fromEntries(await request.formData()) + ...form }; try { diff --git a/src/routes/[username]/settings/+page.svelte b/src/routes/[username]/settings/+page.svelte index c1ac20d0..7bd4ddcc 100644 --- a/src/routes/[username]/settings/+page.svelte +++ b/src/routes/[username]/settings/+page.svelte @@ -1,21 +1,28 @@ <script> + import { fly } from 'svelte/transition'; import { enhance } from '$app/forms'; import Account from './_account.svelte'; import Pos from './_pos.svelte'; import Security from './_security.svelte'; - import { Icon } from '$comp'; + import { Icon, Spinner, Pin } from '$comp'; import { t } from '$lib/translations'; import { failure, success } from '$lib/utils'; - import { avatarUpload, bannerUpload } from '$lib/store'; + import { avatarUpload, bannerUpload, pin } from '$lib/store'; import { upload } from '$lib/upload'; export let data; + export let form; let { user, rates, token } = data; $: update(data); let update = () => ({ user, rates, token } = data); + $: if (form?.message?.startsWith('Pin')) { + failure('Wrong pin, try again'); + $pin = ''; + } + let tab = 'account'; let tabs = [ @@ -26,23 +33,37 @@ $: ({ comp } = tabs.find((t) => t.name === tab)); - let save = async () => { + let loading, submit; + let save = async (msg) => { + loading = true; + if (typeof msg !== 'string') msg = 'Settings saved'; + try { if ($avatarUpload) { await upload($avatarUpload.file, $avatarUpload.type, $avatarUpload.progress, token); } + if ($bannerUpload) { await upload($bannerUpload.file, $bannerUpload.type, $bannerUpload.progress, token); } - success('Settings saved'); + + success(msg); } catch (e) { console.log(e); failure('Something went wrong'); } + + loading = false; }; </script> +{#if user.haspin && $pin?.length !== 6} + <Pin /> +{/if} + <form method="POST" class="mb-[154px]" use:enhance on:submit={save}> + <input type="hidden" name="pin" value={$pin} /> + <div class="mt-24 mb-20 px-3 md:px-0 w-full md:w-[400px] mx-auto space-y-8"> <h1 class="text-center text-3xl md:text-4xl font-semibold mb-10"> {$t('user.settings.header')} @@ -58,16 +79,22 @@ {/each} </div> - <svelte:component this={comp} {user} {rates} /> + <svelte:component this={comp} {user} {rates} {submit} /> </div> <div class="z-10 fixed bottom-0 bg-white shadow border-t w-full flex justify-center items-center py-3" > <button + bind:this={submit} type="submit" - class="bg-black text-white rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80" + class="border-2 border-black rounded-xl font-semibold mx-auto py-3 w-40 hover:opacity-80" + class:bg-black={loading} > - <div class="my-auto">Save Settings</div> + {#if loading} + <Spinner /> + {:else} + <div class="my-auto">Save Settings</div> + {/if} </button> </div> </form> diff --git a/src/routes/[username]/settings/_account.svelte b/src/routes/[username]/settings/_account.svelte index bb229b93..f139b3f4 100644 --- a/src/routes/[username]/settings/_account.svelte +++ b/src/routes/[username]/settings/_account.svelte @@ -17,6 +17,7 @@ $: banner = files.banner.src; $: $tempProfileFiles = { profile: files.profile.src, banner: files.banner.src }; + let togglePassword = () => (revealPassword = !revealPassword); let colorThemes = [ { theme: 1, color1: 'from-[#F5F7FA]', color2: 'to-[#C3CFE2]' }, { theme: 2, color1: 'from-[#FDFCFB]', color2: 'to-[#E2D1C3]' }, @@ -36,9 +37,14 @@ percent = Math.round((event.loaded / event.total) * 100); }; + let tooLarge = {}; + let files = { profile: {}, banner: {} }; let handleFile = async ({ target }, type) => { + tooLarge[type] = false; let file = target.files[0]; + console.log('TYPE', file.size); + if (file.size > 5000000) return (tooLarge[type] = true); var reader = new FileReader(); reader.onload = async (e) => { files[type].src = e.target.result; @@ -83,7 +89,8 @@ {/if} <button type="button" - on:click={() => (revealPassword = !revealPassword)} + on:click={togglePassword} + on:keydown={togglePassword} class="absolute right-5 top-11" > <Icon icon={revealPassword ? 'eye' : 'eye-off'} /> @@ -118,11 +125,13 @@ <div> <span class="font-bold">{$t('user.settings.profileImage')}</span> + <div class="flex"> {#if profile} <div class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer" on:click={() => select('profile')} + on:keydown={() => select('profile')} > <img src={profile} @@ -134,9 +143,10 @@ <div class="relative rounded-full overflow-hidden text-center w-20 h-20 my-auto hover:opacity-80 cursor-pointer" on:click={() => select('profile')} + on:keydown={() => select('profile')} > <img - src={`/api/public/${user.username}-profile.png`} + src={`/api/public/${user.username}-profile.webp`} class="absolute w-full h-full object-cover object-center visible overflow-hidden" alt={user.username} /> @@ -145,6 +155,7 @@ <div class="rounded-full border-4 border-white p-4 bg-gradient-to-r {$colorTheme} w-24 my-auto hover:opacity-80 cursor-pointer" on:click={() => select('profile')} + on:keydown={() => select('profile')} > <Icon icon="logo-symbol-white" style="mx-auto" /> </div> @@ -154,7 +165,8 @@ <button type="button" class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" - on:click={() => select('profile')}>Select</button + on:click={() => select('profile')} + on:keydown={() => select('profile')}>Select</button > <input type="file" @@ -167,6 +179,10 @@ {/if} </div> </div> + + {#if tooLarge['profile']} + <div class="text-red-600">Max file size 5MB</div> + {/if} </div> <div> @@ -180,19 +196,22 @@ src={banner} class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80" on:click={() => select('banner')} + on:keydown={() => select('banner')} alt="Banner" /> {:else if user.banner} <img - src={`/api/public/${user.username}-banner.png`} + src={`/api/public/${user.username}-banner.webp`} class="w-full object-cover object-center visible overflow-hidden h-48 mb-4 hover:opacity-80" on:click={() => select('banner')} + on:keydown={() => select('banner')} alt="Banner" /> {:else} <div class="bg-gradient-to-r {$colorTheme} w-full h-48 mb-4 cursor-pointer hover:opacity-80" on:click={() => select('banner')} + on:keydown={() => select('banner')} alt="Banner" /> {/if} @@ -201,7 +220,8 @@ <button type="button" class="border rounded-2xl font-bold w-24 text-center px-0 py-2 hover:opacity-80" - on:click={() => select('banner')}>Select</button + on:click={() => select('banner')} + on:keydown={() => select('banner')}>Select</button > <input type="file" @@ -212,6 +232,10 @@ {#if files.banner.filename} <div class="mt-2 text-sm">{files.banner.filename}</div> {/if} + + {#if tooLarge['banner']} + <div class="text-red-600">Max file size 5MB</div> + {/if} </div> <!-- TODO @@ -226,6 +250,7 @@ ? 'border-black' : ''}" on:click={() => handleThemeClick(colors)} + on:keydown={() => handleThemeClick(colors)} /> {/each} </div> --> diff --git a/src/routes/[username]/settings/_security.svelte b/src/routes/[username]/settings/_security.svelte index 2a490aa4..98b9b503 100644 --- a/src/routes/[username]/settings/_security.svelte +++ b/src/routes/[username]/settings/_security.svelte @@ -4,26 +4,80 @@ import { Icon, Qr } from '$comp'; import { Pincode, PincodeInput } from 'svelte-pincode'; import { post, success, failure } from '$lib/utils'; + import { pin as current } from '$lib/store'; - export let user; + export let user, submit; let settingPin, setting2fa, disabling2fa; - let code = []; - let pin = ''; let token = ''; - let tokenInput, pinInput; + let pinCode = []; + let verifyCode = []; + let tokenInput, pinInput, verifyInput; + let verifying = false; + let old; + let startVerifying = () => { + verifying = true; + }; + + $: pin = pinCode.join(''); + + let locked; + $: verifyCode.join('').length > 5 && checkPin(); + let checkPin = async () => { + old = $current; + if (locked) return; + locked = true; + + let verify = verifyCode.join(''); + + pinCode = ['', '', '', '', '', '']; + verifyCode = ['', '', '', '', '', '']; + settingPin = false; + + try { + if (pin.length > 5 && pin === verify) { + user.haspin = true; + $current = pin; + submit.click(); + } else { + failure('Pin mismatch, try again'); + settingPin = true; + pinInput.focusFirstInput(); + } + } catch (e) { + failure('Problem setting PIN'); + } + + verifying = false; + + await new Promise((r) => setTimeout(r, 500)); + locked = false; + }; + + $: if (verifying) verifyInput && tick().then(verifyInput.focusFirstInput); $: if (settingPin) pinInput && tick().then(pinInput.focusFirstInput); - $: if (setting2fa || disabling2fa) tokenInput && tick().then(tokenInput.focusFirstInput); + $: if (setting2fa || disabling2fa) tokenInput && tick().then(tokenInput?.focusFirstInput); let reset = () => { token = ''; - code = []; return true; }; - let togglePin = () => (settingPin = !settingPin); + let togglePin = async () => { + if (user.haspin) { + try { + pin = null; + submit.click(); + } catch (e) { + failure('Failed to disable pin'); + } + } else { + settingPin = true; + } + }; + let toggleEnabling = () => reset() && (setting2fa = !setting2fa); let toggleDisabling = () => reset() && (disabling2fa = !disabling2fa); @@ -58,31 +112,44 @@ $: otpUri = `otpauth://totp/coinos:${user.username}?secret=${user.otpsecret}&period=30&digits=6&algorithm=SHA1&issuer=coinos`; </script> -<!-- TODO <div> - <span class="font-bold mb-1">{$t('user.settings.securityPIN')}</span> + <input type="hidden" name="newpin" value={pin} /> + + <span class="font-bold mb-1">{verifying ? 'Verify' : 'Security'} PIN</span> <p class="text-secondary mb-1"> {$t('user.settings.securityPINDescription')} </p> {#if settingPin} <div class="flex my-4"> <div class="mx-auto"> - <Pincode bind:code bind:value={user.pin} bind:this={pinInput}> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - <PincodeInput /> - </Pincode> + <div class:hidden={verifying}> + <Pincode on:complete={startVerifying} bind:code={pinCode} bind:this={pinInput}> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + </Pincode> + </div> + <div class:hidden={!verifying}> + <Pincode bind:code={verifyCode} bind:this={verifyInput}> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + <PincodeInput /> + </Pincode> + </div> </div> </div> {:else} <button type="button" class="primary" on:click={togglePin} - ><Icon icon="lock" style="mr-1" /> {$t('user.settings.setPIN')}</button + ><Icon icon="lock" style="mr-1" /> {user.haspin ? 'Disable Pin' : 'Enable Pin'}</button > {/if} -</div> --> +</div> <!-- TODO <div> @@ -116,7 +183,7 @@ <div class="text-center my-4"> {$t('user.settings.oneTimeCode')}<br /> - <Pincode bind:code bind:value={token} bind:this={tokenInput}> + <Pincode bind:value={token} bind:this={tokenInput}> <PincodeInput /> <PincodeInput /> <PincodeInput /> @@ -128,7 +195,7 @@ {:else if disabling2fa} <div class="text-center my-4"> {$t('user.settings.oneTimeCode')}<br /> - <Pincode bind:code bind:value={token} bind:this={tokenInput}> + <Pincode bind:value={token} bind:this={tokenInput}> <PincodeInput /> <PincodeInput /> <PincodeInput /> diff --git a/src/routes/invoice/[id]/+page.svelte b/src/routes/invoice/[id]/+page.svelte index 04a31813..84b03593 100644 --- a/src/routes/invoice/[id]/+page.svelte +++ b/src/routes/invoice/[id]/+page.svelte @@ -44,7 +44,9 @@ $: tip = Math.round((amount / 100) * tipPercent); $invoices[id] = { amount, id, rate, received, text, tip, username }; - $: browser && $invoices[id]?.received >= $invoices[id]?.amount && goto(`/invoice/${id}/paid`); + $: browser && + $invoices[id]?.received >= $invoices[id]?.amount && + goto(($invoices[id].memo === 'launch' && '/launch/purchase') || `/invoice/${id}/paid`); $: amountFiat = parseFloat(((amount * rate) / sats).toFixed(2)); diff --git a/src/routes/invoice/[id]/paid/+page.svelte b/src/routes/invoice/[id]/paid/+page.svelte index 95c721d0..d619b7ff 100644 --- a/src/routes/invoice/[id]/paid/+page.svelte +++ b/src/routes/invoice/[id]/paid/+page.svelte @@ -22,7 +22,7 @@ }; </script> -<div class="container px-4 text-center"> +<div class="container px-4 text-center mx-auto"> <div class="flex w-full py-20 max-w-[200px] mx-auto" in:scale={{ start: 0.5 }}> <Icon icon="check" style="mx-auto" /> </div> diff --git a/src/routes/launch/+page.server.js b/src/routes/launch/+page.server.js new file mode 100644 index 00000000..2b44dfae --- /dev/null +++ b/src/routes/launch/+page.server.js @@ -0,0 +1,6 @@ +import { auth, get } from '$lib/utils'; + +export const load = async ({ parent }) => { + let tickets = await get('/tickets'); + return { ...(await parent()), tickets }; +}; diff --git a/src/routes/launch/+page.svelte b/src/routes/launch/+page.svelte index 04f53086..6faf492d 100644 --- a/src/routes/launch/+page.svelte +++ b/src/routes/launch/+page.svelte @@ -1,48 +1,99 @@ <script> - import { redirect } from '$lib/store'; + import { LandingHeader } from '$comp'; + import { loginRedirect } from '$lib/store'; import { beforeNavigate } from '$app/navigation'; import { info } from '$lib/utils'; + import { Icon } from '$comp'; + import { t } from '$lib/translations'; + import { goto } from '$app/navigation'; + import { scroll } from '$lib/utils'; export let data; - let { user } = data; - $: link = user ? '/launch/purchase' : '/register'; - $redirect = '/launch/purchase'; + let { user, tickets } = data; + $loginRedirect = '/launch/purchase'; + + let hasTicket = !!user?.accounts.find((a) => tickets.includes(a.asset)); + $: link = hasTicket ? '/launch/ticket' : user ? $loginRedirect : '/register'; + $: text = (hasTicket ? 'View' : 'Purchase') + ' Ticket'; beforeNavigate(({ to }) => { - console.log(link); - if (link === '/register') info('Please sign in so we can get you a ticket'); + if (to?.routeId === 'register') info('Please sign in so we can get you a ticket'); }); + + let showMobileMenu = false; + const mobileMenuButtonClick = (section) => { + showMobileMenu = false; + scroll(section); + }; </script> -<div class="flex mt-20 px-8"> - <img src="/images/ticket.webp" class="mb-8 md:max-w-xl mx-auto border border-gray-400" /> -</div> -<div class="mx-auto flex items-center justify-center px-8"> - <div class="flex flex-col w-full bg-white rounded shadow-lg sm:w-3/4 md:w-1/2 lg:w-3/5"> +<header class="w-full lg:w-5/6 py-5 mx-auto fixed md:sticky z-10 top-0 bg-white/90"> + <nav class="block xl:flex flex-wrap justify-between items-center"> + <div class="flex justify-start md:justify-center items-center md:space-x-10"> + <a href="/" class="hidden md:block"> + <Icon icon="logo" style="mb-5 xl:mb-0" /> + </a> + <Icon icon="logo" style="block md:hidden ml-5 w-48" /> + </div> + </nav> +</header> + +<div class="mx-auto flex items-center justify-center px-8 mt-20"> + <div class="flex flex-col w-full bg-white rounded shadow-lg sm:w-3/4 lg:w-3/5"> <div class="flex flex-col w-full md:flex-row"> <div class="flex flex-row justify-around p-4 font-bold leading-none text-gray-800 uppercase bg-gray-400 rounded md:flex-col md:items-center md:justify-center md:w-1/4" > <div class="md:text-3xl">Oct</div> - <div class="md:text-6xl">5</div> + <div class="md:text-6xl">26</div> <div class="md:text-xl">6 pm</div> </div> - <div class="p-4 font-normal text-gray-800 md:w-3/4"> + <div class="p-4 font-normal text-gray-800 md:w-3/4 space-y-5"> <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800"> - Coinos Launch Party! + Launch Party! </h1> + <p class="leading-normal mb-2"> - Come join us at our launch party celebrating 10 years in business! We'll be introducing - our newly designed payments interface and afterwards we'll network and enjoy tasty food - and drinks from <a href="https://loftondenman.com/" class="underline">The Loft</a>! + Come join us at our launch party celebrating our new design and 10 years serving the + Vancouver Bitcoin community! + </p> + + <div class="flex"> + <div class="space-y-5"> + <div class="ml-8"> + <p><b>When</b></p> + <p>6pm October 26</p> + </div> + <div class="ml-8"> + <p><b>Where</b></p> + <p>The Loft Lounge</p> + <p>1184 Denman Street, #203</p> + <p>Vancouver, BC</p> + </div> + </div> + + <img src="/icons/party.svg" alt="Partygoers" class="w-[120px] mx-auto" /> + </div> + + <p> + We'll introduce our new interface and then network and enjoy tasty food and drinks from <a + href="https://loftondenman.com/" + class="underline">The Loft</a + >! + </p> + + <p> + The Loft will be accepting Lightning payments at the event and going forward into the + future as well! We're charging $6 for for an NFT ticket that gets you entry, appies, and the + chance to win some swag and sats. </p> <div class="flex w-full my-8"> - <a href={link}> + <a href={link} class="mx-auto"> <button - class="bg-white text-black border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl" + class="bg-white text-black border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" > - Purchase Ticket + {text} </button> </a> </div> diff --git a/src/routes/launch/purchase/+page.server.js b/src/routes/launch/purchase/+page.server.js index f6314f7f..2ecce695 100644 --- a/src/routes/launch/purchase/+page.server.js +++ b/src/routes/launch/purchase/+page.server.js @@ -1,8 +1,45 @@ -export const load = ({ parent }) => parent(); +import { auth, get, post } from '$lib/utils'; +import tickets from '$lib/tickets'; +import { redirect } from '@sveltejs/kit'; +import { env } from '$env/dynamic/private'; +import { invalidateAll } from '$app/navigation'; +import { browser } from '$app/environment'; +import { createTicket } from '$lib/ticket'; + +export const load = async ({ parent }) => { + let tickets = await get('/tickets'); + let { user } = await parent(); + let hasTicket = !!user?.accounts.find((a) => tickets.includes(a.asset)); + if (hasTicket) throw redirect(307, '/launch/ticket'); + return { tickets }; +}; export const actions = { - default: async ({ cookies, request }) => { - let form = Object.fromEntries(await request.formData()); - console.log(form); + internal: async ({ cookies, request }) => { + let form = await request.formData(); + let asset = await createTicket(cookies, form.get('username')); + await post('/send', { amount: 22000, username: 'launch' }, auth(cookies)); + + throw redirect(307, `/launch/thanks/${asset}`); + }, + + lightning: async ({ cookies, request }) => { + let form = await request.formData(); + + let rates = await get('/rates'); + + let invoice = { + amount: 22000, + memo: 'launch', + network: 'lightning', + prompt: false, + rate: rates[form.get('currency')] + }; + + let user = { username: form.get('username') }; + + let { uuid } = await post('/invoice', { invoice, user }, auth(cookies)); + + throw redirect(307, `/invoice/${uuid}`); } }; diff --git a/src/routes/launch/purchase/+page.svelte b/src/routes/launch/purchase/+page.svelte index df3048f3..3273752a 100644 --- a/src/routes/launch/purchase/+page.svelte +++ b/src/routes/launch/purchase/+page.svelte @@ -1,4 +1,5 @@ <script> + import { onMount } from 'svelte'; import { enhance } from '$app/forms'; import { selectedRate } from '$lib/store'; import { post } from '$lib/utils'; @@ -6,77 +7,61 @@ export let data; - let amount = 35000; + let amount = 22000; let { + currency, account: { balance }, username } = data.user; - let invoice; - let get = async (network) => { - let { uuid } = await post('/invoice', { - amount, - rate: $selectedRate, - network, - username - }); - - goto(`/invoice/${uuid}`); - }; - - let lightning = () => get('lightning'); - let bitcoin = () => get('bitcoin'); - - // if (balance < amount) throw redirect(307, `/${username}/fund/${amount - balance}`); + let { tickets } = data; + let ticket = tickets.length; </script> -<div class="mx-auto h-screen flex items-center justify-center px-8"> - <div class="p-4 font-normal text-gray-800 mx-auto mb-8"> - {#if balance > amount} - <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center"> - Buy Your Launch Party Ticket? - </h1> +<div class="mx-auto h-screen flex items-center justify-center px-4"> + <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8"> + <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center"> + Purchase Ticket + </h1> - <div class="text-center text-4xl mb-12">{amount} sats</div> + <img + src={`/tickets/${(ticket + 1).toString().padStart(2, '0')}.png`} + class="w-full max-w-[800px]" + alt="Ticket" + /> - <div class="w-full flex"> - <form method="POST" use:enhance> + {#if balance >= amount} + <form action="?/internal" method="POST" use:enhance> + <input type="hidden" name="username" value={username} /> + + <div class="w-full flex"> <button class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl" > Count me in </button> - </form> - </div> + </div> + </form> {:else} - <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center"> - Ticket Price - </h1> - <div class="text-center text-4xl mb-12">{amount} sats</div> - - <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center"> - Your Balance - </h1> - - <div class="text-center text-4xl mb-12">{balance} sats</div> - - <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center"> - Funding Options + <h1 class="mb-4 text-xl font-bold leading-none tracking-tight text-gray-800 text-center"> + Payment Options </h1> - <button - class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl" - on:click={lightning} - > - Lightning - </button> + <div class="flex justify-center gap-4"> + <form action="?/lightning" method="POST"> + <input type="hidden" name="currency" value={currency} /> + <input type="hidden" name="username" value={username} /> + <button class="border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl"> + Lightning + </button> + </form> - <button - class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl" - on:click={bitcoin} - > - Bitcoin - </button> + <a href={`purchase/card`}> + <button class="border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-2xl"> + Credit Card + </button> + </a> + </div> {/if} </div> </div> diff --git a/src/routes/launch/purchase/card/+page.server.js b/src/routes/launch/purchase/card/+page.server.js new file mode 100644 index 00000000..1f564f78 --- /dev/null +++ b/src/routes/launch/purchase/card/+page.server.js @@ -0,0 +1,44 @@ +import { env } from '$env/dynamic/private'; +import { error, redirect } from '@sveltejs/kit'; +import { post, auth } from '$lib/utils'; +import { createTicket } from '$lib/ticket'; + +let stripe = 'https://api.stripe.com/v1'; + +export const actions = { + default: async ({ cookies, request }) => { + let form = await request.formData(); + + let headers = new Headers(); + headers.set('Authorization', 'Basic ' + new Buffer(env.STRIPE + ':').toString('base64')); + + let body = new URLSearchParams({ + 'card[number]': form.get('number').replace(/\s+/g, ''), + 'card[exp_month]': form.get('expiry').slice(0, 2), + 'card[exp_year]': form.get('expiry').slice(-2), + 'card[cvc]': form.get('cvc') + }); + + let method = 'POST'; + + let { id: source } = await fetch(`${stripe}/tokens`, { body, headers, method }).then((r) => + r.json() + ); + + body = new URLSearchParams({ + amount: 600, + currency: 'cad', + source, + description: 'launch ticket' + }); + + let { status } = await fetch(`${stripe}/charges`, { body, headers, method }).then((r) => + r.json() + ); + + if (status !== 'succeeded') throw error(500, { message: 'Card payment failed' }); + let asset = await createTicket(cookies, form.get('username')); + + throw redirect(307, `/launch/thanks/${asset}`); + } +}; diff --git a/src/routes/launch/purchase/card/+page.svelte b/src/routes/launch/purchase/card/+page.svelte new file mode 100644 index 00000000..05d09025 --- /dev/null +++ b/src/routes/launch/purchase/card/+page.svelte @@ -0,0 +1,77 @@ +<script> + import { enhance } from '$app/forms'; + import { Lock, Card, Calendar, Spinner } from '$comp'; + + export let data; + + let { username } = data.user; + let loading; +</script> + +<div class="container max-w-lg px-4 mx-auto space-y-5"> + <form + method="POST" + class="flex flex-wrap gap-5 w-full p-5 mt-20" + use:enhance={() => { + loading = true; + return ({ update }) => update() && (loading = false); + }} + > + <input type="hidden" name="username" value={username} /> + <label class="relative w-full flex flex-col"> + <span class="font-bold mb-3">Card number</span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="number" + placeholder="0000 0000 0000 0000" + /> + <Card /> + </label> + + <label class="relative flex-1 flex flex-col"> + <span class="font-bold mb-3">Expire date</span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="expiry" + placeholder="MM/YY" + /> + <Calendar /> + </label> + + <label class="relative flex-1 flex flex-col"> + <span class="font-bold flex items-center gap-3 mb-3"> + CVC/CVV + <span class="relative group"> + <span + class="hidden group-hover:flex justify-center items-center px-2 py-1 text-xs absolute -right-2 transform translate-x-full -translate-y-1/2 w-max top-1/2 bg-black text-white" + > + 3-digit number on the back of your card</span + > + </span> + </span> + <input + class="rounded-md peer pl-12 pr-2 py-2 border-2 border-gray-200 placeholder-gray-300" + type="text" + name="cvc" + placeholder="•••" + /> + <Lock /> + </label> + + <div class="w-full flex mt-8"> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 mx-auto text-xl" + type="submit" + disabled={loading} + > + {#if loading} + <Spinner /> + {:else} + Submit + {/if} + </button> + </div> + </form> +</div> diff --git a/src/routes/launch/thanks/[asset]/+page.server.js b/src/routes/launch/thanks/[asset]/+page.server.js new file mode 100644 index 00000000..e9e01376 --- /dev/null +++ b/src/routes/launch/thanks/[asset]/+page.server.js @@ -0,0 +1 @@ +// export const load = ({ parent }) => parent(); diff --git a/src/routes/launch/thanks/[asset]/+page.svelte b/src/routes/launch/thanks/[asset]/+page.svelte new file mode 100644 index 00000000..43f10057 --- /dev/null +++ b/src/routes/launch/thanks/[asset]/+page.svelte @@ -0,0 +1,29 @@ +<script> + import { page } from '$app/stores'; + + export let data; + + let { user } = data; + + let { asset } = $page.params; +</script> + +<div class="container mx-auto max-w-lg px-4 mt-20"> + <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8"> + <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center"> + Thanks! + </h1> + + <p>We look forward to seeing you!</p> + </div> + + <div class="w-full flex"> + <a href="/launch/ticket" class="mx-auto"> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" + > + Done + </button> + </a> + </div> +</div> diff --git a/src/routes/launch/ticket/+page.server.js b/src/routes/launch/ticket/+page.server.js new file mode 100644 index 00000000..0362eb7d --- /dev/null +++ b/src/routes/launch/ticket/+page.server.js @@ -0,0 +1,9 @@ +import { get } from '$lib/utils'; + +export const load = async ({ cookies, parent }) => { + let { user } = await parent(); + let tickets = await get('/tickets'); + let ticket = tickets.findIndex((t) => user.accounts.find((a) => a.asset === t)); + let asset = tickets[ticket]; + return { asset, ticket }; +}; diff --git a/src/routes/launch/ticket/+page.svelte b/src/routes/launch/ticket/+page.svelte new file mode 100644 index 00000000..3879abcd --- /dev/null +++ b/src/routes/launch/ticket/+page.svelte @@ -0,0 +1,35 @@ +<script> + export let data; + let { asset, ticket } = data; +</script> + +<div class="mx-auto h-screen flex items-center justify-center px-4"> + <div class="font-normal text-gray-800 mx-auto mb-8 space-y-8"> + <h1 class="mb-4 text-4xl font-bold leading-none tracking-tight text-gray-800 text-center"> + Your Ticket + </h1> + + <p> + We've minted your ticket as an <a + href={`https://blockstream.info/liquid/asset/${asset}`} + class="underline">NFT on the Liquid Blockchain</a + > as a proof of purchase and commemorative token. + </p> + + <img + src={`/tickets/${(ticket + 1).toString().padStart(2, '0')}.png`} + class="w-full max-w-[800px]" + alt="Ticket" + /> + + <div class="w-full flex"> + <a href="/" class="mx-auto"> + <button + class="bg-black text-white border rounded-full px-8 py-4 font-bold hover:opacity-80 text-2xl" + > + Done + </button> + </a> + </div> + </div> +</div> diff --git a/src/routes/login/+page.server.js b/src/routes/login/+page.server.js index 94981ff7..b46af49e 100644 --- a/src/routes/login/+page.server.js +++ b/src/routes/login/+page.server.js @@ -4,15 +4,15 @@ import { login } from '$lib/utils'; export const actions = { default: async ({ cookies, request }) => { let form = Object.fromEntries(await request.formData()); - let { username, password, redirect: r } = form; - let user = { username, password }; + let { username, password, token, loginRedirect } = form; + let user = { username, password, token }; try { - await login(user, cookies); + let r = await login(user, cookies); } catch (e) { - return invalid(400, { error: 'Login failed' }); + return invalid(400, { error: 'Login failed', message: e.message, ...form }); } - throw redirect(303, r || `/${user.username}/dashboard`); + throw redirect(303, loginRedirect || `/${user.username}/dashboard`); } }; diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte index cf7cb517..75998cd0 100644 --- a/src/routes/login/+page.svelte +++ b/src/routes/login/+page.svelte @@ -1,5 +1,6 @@ <script> import { RegisterOrLogin } from '$comp'; + export let form; </script> diff --git a/src/routes/register/+page.server.js b/src/routes/register/+page.server.js index bfe1a9b7..c5751d4c 100644 --- a/src/routes/register/+page.server.js +++ b/src/routes/register/+page.server.js @@ -4,7 +4,7 @@ import { post, login } from '$lib/utils'; export const actions = { default: async ({ cookies, request }) => { let form = Object.fromEntries(await request.formData()); - let { username, password, redirect: r } = form; + let { username, password, loginRedirect } = form; let user = { username, password }; let error; @@ -23,6 +23,6 @@ export const actions = { } if (error) return invalid(400, { error }); - throw redirect(307, r || `/${user.username}/dashboard`); + throw redirect(307, loginRedirect || `/${user.username}/dashboard`); } }; diff --git a/src/routes/register/+page.svelte b/src/routes/register/+page.svelte index aa0e66e0..fd9e4417 100644 --- a/src/routes/register/+page.svelte +++ b/src/routes/register/+page.svelte @@ -5,9 +5,7 @@ afterNavigate(async () => { try { - console.log('whoa'); await invalidateAll(); - console.log('no'); } catch (e) { console.log(e); } diff --git a/src/routes/scan/+page.svelte b/src/routes/scan/+page.svelte index 1c14cb6d..dc6e291d 100644 --- a/src/routes/scan/+page.svelte +++ b/src/routes/scan/+page.svelte @@ -7,7 +7,7 @@ let scanner, vid; onMount(() => { - scanner = new QrScanner(vid, (text) => goto(`/send/${text}`)); + scanner = new QrScanner(vid, (text) => goto(`/send/${text}`), { highlightScanRegion: true, highlightCodeOutline: true }); scanner.start(); }); diff --git a/src/routes/send/+layout.svelte b/src/routes/send/+layout.svelte new file mode 100644 index 00000000..c9122466 --- /dev/null +++ b/src/routes/send/+layout.svelte @@ -0,0 +1,18 @@ +<script> + import { Pin } from '$comp'; + import { pin } from '$lib/store'; + import { onMount } from 'svelte'; + + export let data; + + let loaded; + let { user } = data; + + onMount(() => setTimeout(() => (loaded = true), 50)); +</script> + +{#if loaded && user.haspin && $pin?.length !== 6} + <Pin /> +{/if} + +<slot /> diff --git a/src/routes/send/[...text]/+page.server.js b/src/routes/send/[...text]/+page.server.js index 45bb597d..b909ab4d 100644 --- a/src/routes/send/[...text]/+page.server.js +++ b/src/routes/send/[...text]/+page.server.js @@ -55,6 +55,6 @@ export const actions = { const form = await request.formData(); await parse(form.get('text')); - return invalid(403, { error: 'Failed to recognize input' }); + return invalid(403, { error: 'Does not compute, try again' }); } }; diff --git a/src/routes/send/[...text]/+page.svelte b/src/routes/send/[...text]/+page.svelte index 5d6d123e..fe949617 100644 --- a/src/routes/send/[...text]/+page.svelte +++ b/src/routes/send/[...text]/+page.svelte @@ -92,8 +92,4 @@ line-height: 20px; @apply block border rounded-2xl p-3 py-4 w-full; } - - .expandable-expandable::-webkit-resizer { - background: transparent; - } </style> diff --git a/src/routes/send/[uuid=uuid]/+page.server.js b/src/routes/send/[uuid=uuid]/+page.server.js index 661e9e7b..c9c77f70 100644 --- a/src/routes/send/[uuid=uuid]/+page.server.js +++ b/src/routes/send/[uuid=uuid]/+page.server.js @@ -10,14 +10,19 @@ export async function load({ params, parent }) { export const actions = { default: async ({ cookies, params, request }) => { - let body = Object.fromEntries(await request.formData()); - let { confirmed } = body; + try { + let body = Object.fromEntries(await request.formData()); + let { confirmed } = body; - if (!confirmed) { - return invalid(403, { amount, confirm: true }); + if (!confirmed) { + return invalid(403, { amount, confirm: true }); + } + + await post('/send', body, auth(cookies)); + } catch (e) { + return invalid(400, { message: e.message }); } - await post('/send', body, auth(cookies)); throw redirect(307, '/sent'); } }; diff --git a/src/routes/send/[uuid=uuid]/+page.svelte b/src/routes/send/[uuid=uuid]/+page.svelte index aa798bc1..73712f71 100644 --- a/src/routes/send/[uuid=uuid]/+page.svelte +++ b/src/routes/send/[uuid=uuid]/+page.svelte @@ -1,6 +1,6 @@ <script> import { goto } from '$app/navigation'; - import { selectedRate } from '$lib/store'; + import { pin, selectedRate } from '$lib/store'; import { enhance } from '$app/forms'; import { Icon, Numpad, Spinner } from '$comp'; import { page } from '$app/stores'; @@ -31,12 +31,24 @@ let loading; let submit = () => (loading = true); + + $: update(form); + let update = () => { + if (form?.message.includes('pin')) $pin = undefined; + loading = false; + }; </script> <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={handleBack}> <Icon icon="arrow-left" style="w-10" /> </button> +{#if form?.message} + <div class="text-red-600 text-center"> + {form.message} + </div> +{/if} + <div class="container px-4 mt-20 max-w-xl mx-auto"> {#if amount} <div class="text-center mb-8"> @@ -61,6 +73,7 @@ <input name="payreq" value={payreq} type="hidden" /> <input name="username" value={recipient.username} type="hidden" /> <input name="confirmed" value={form?.confirm} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> <div class="flex w-full"> {#if amount} diff --git a/src/routes/send/bitcoin/[address]/+page.server.js b/src/routes/send/bitcoin/[address]/+page.server.js index 9b29a6da..642593ab 100644 --- a/src/routes/send/bitcoin/[address]/+page.server.js +++ b/src/routes/send/bitcoin/[address]/+page.server.js @@ -1,11 +1,16 @@ -import { redirect } from '@sveltejs/kit'; +import { invalid, redirect } from '@sveltejs/kit'; import { auth, post } from '$lib/utils'; export const actions = { default: async ({ cookies, request }) => { - let { address, amount } = Object.fromEntries(await request.formData()); - let { tx } = await post('/bitcoin/fee', { address, amount }, auth(cookies)); - await post('/bitcoin/send', { address, tx }, auth(cookies)); + try { + let { address, amount, pin } = Object.fromEntries(await request.formData()); + let { tx } = await post('/bitcoin/fee', { address, amount }, auth(cookies)); + await post('/bitcoin/send', { address, pin, tx }, auth(cookies)); + } catch (e) { + return invalid(400, { message: e.message }); + } + throw redirect(307, '/sent'); } }; diff --git a/src/routes/send/bitcoin/[address]/+page.svelte b/src/routes/send/bitcoin/[address]/+page.svelte index 8c9e72ff..c9023180 100644 --- a/src/routes/send/bitcoin/[address]/+page.svelte +++ b/src/routes/send/bitcoin/[address]/+page.svelte @@ -3,19 +3,33 @@ import { Icon, Numpad, Spinner } from '$comp'; import { page } from '$app/stores'; import { back } from '$lib/utils'; + import { pin } from '$lib/store'; + export let data; + export let form; let { address } = $page.params; let { currency } = data.user; let amount, loading; let submit = () => (loading = true); + $: update(form); + let update = () => { + if (form?.message.includes('pin')) $pin = undefined; + loading = false; + }; </script> <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}> <Icon icon="arrow-left" style="w-10" /> </button> +{#if form?.message} + <div class="text-red-600 text-center"> + {form.message} + </div> +{/if} + <div class="container px-4 mt-20 max-w-xl mx-auto"> <div class="text-center mb-8"> <h1 class="text-3xl md:text-4xl font-semibold mb-2">Send to</h1> @@ -26,6 +40,8 @@ <form method="POST" use:enhance on:submit={submit}> <input name="address" value={address} type="hidden" /> <input name="amount" value={amount} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> + <div class="flex w-full"> <button type="submit" diff --git a/src/routes/send/lightning/[payreq]/+page.server.js b/src/routes/send/lightning/[payreq]/+page.server.js index f17d6a60..49ebac1c 100644 --- a/src/routes/send/lightning/[payreq]/+page.server.js +++ b/src/routes/send/lightning/[payreq]/+page.server.js @@ -7,14 +7,20 @@ export async function load({ params }) { export const actions = { default: async ({ cookies, request }) => { - let body = Object.fromEntries(await request.formData()); - let { amount, confirmed } = body; + try { + let body = Object.fromEntries(await request.formData()); + let { amount, confirmed } = body; - if (!confirmed) { - return invalid(403, { amount, confirm: true }); + if (!confirmed) { + return invalid(403, { amount, confirm: true }); + } + + await post('/lightning/send', body, auth(cookies)); + } catch (e) { + if (e.message.includes("unusable")) e.message = "Failed to route payment, try sending a lower amount" + return invalid(400, { message: e.message }); } - await post('/lightning/send', body, auth(cookies)); throw redirect(307, '/sent'); } }; diff --git a/src/routes/send/lightning/[payreq]/+page.svelte b/src/routes/send/lightning/[payreq]/+page.svelte index 973550f6..bda10038 100644 --- a/src/routes/send/lightning/[payreq]/+page.svelte +++ b/src/routes/send/lightning/[payreq]/+page.svelte @@ -3,6 +3,8 @@ import { Icon, Numpad, Spinner } from '$comp'; import { page } from '$app/stores'; import { back, s } from '$lib/utils'; + import { pin } from '$lib/store'; + export let data; export let form; @@ -14,12 +16,23 @@ let loading; let submit = () => (loading = true); + $: update(form); + let update = () => { + if (form?.message.includes('pin')) $pin = undefined; + loading = false; + }; </script> <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={back}> <Icon icon="arrow-left" style="w-10" /> </button> +{#if form?.message} + <div class="text-red-600 text-center"> + {form.message} + </div> +{/if} + <div class="container px-4 mt-20 max-w-xl mx-auto"> {#if amount} <div class="text-center mb-8"> @@ -38,6 +51,7 @@ <input name="payreq" value={payreq} type="hidden" /> <input name="amount" value={amount} type="hidden" /> <input name="confirmed" value={form?.confirm} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> <div class="flex w-full"> <button diff --git a/src/routes/send/user/[username]/[...amount]/+page.server.js b/src/routes/send/user/[username]/[...amount]/+page.server.js index 12bae186..a659eef3 100644 --- a/src/routes/send/user/[username]/[...amount]/+page.server.js +++ b/src/routes/send/user/[username]/[...amount]/+page.server.js @@ -11,14 +11,19 @@ export async function load({ params, parent }) { export const actions = { default: async ({ cookies, request }) => { - let body = Object.fromEntries(await request.formData()); - let { amount, confirmed } = body; + try { + let body = Object.fromEntries(await request.formData()); + let { amount, confirmed } = body; - if (!confirmed) { - return invalid(403, { amount, confirm: true }); + if (!confirmed) { + return invalid(403, { amount, confirm: true }); + } + + await post('/send', body, auth(cookies)); + } catch (e) { + return invalid(400, { message: e.message }); } - await post('/send', body, auth(cookies)); throw redirect(307, '/sent'); } }; diff --git a/src/routes/send/user/[username]/[...amount]/+page.svelte b/src/routes/send/user/[username]/[...amount]/+page.svelte index e50acd5b..6f4cb313 100644 --- a/src/routes/send/user/[username]/[...amount]/+page.svelte +++ b/src/routes/send/user/[username]/[...amount]/+page.svelte @@ -1,10 +1,11 @@ <script> import { goto } from '$app/navigation'; - import { selectedRate } from '$lib/store'; + import { pin, selectedRate } from '$lib/store'; import { enhance } from '$app/forms'; import { Avatar, Icon, Numpad, Spinner } from '$comp'; import { page } from '$app/stores'; import { back, f, s, sats } from '$lib/utils'; + export let data; export let form; @@ -31,12 +32,24 @@ let loading; let submit = () => (loading = true); + + $: update(form); + let update = () => { + if (form?.message.includes('pin')) $pin = undefined; + loading = false; + }; </script> <button class="ml-5 md:ml-20 mt-5 md:mt-10 hover:opacity-80" on:click={handleBack}> <Icon icon="arrow-left" style="w-10" /> </button> +{#if form?.message} + <div class="text-red-600 text-center"> + {form.message} + </div> +{/if} + <div class="container px-4 mt-20 max-w-xl mx-auto"> {#if amount} <div class="text-center mb-8"> @@ -63,6 +76,7 @@ <input name="amount" value={amount} type="hidden" /> <input name="username" value={recipient.username} type="hidden" /> <input name="confirmed" value={form?.confirm} type="hidden" /> + <input name="pin" value={$pin} type="hidden" /> <div class="flex w-full"> {#if amount} diff --git a/src/routes/user/+server.js b/src/routes/user/+server.js index c9490984..b9c37e6e 100644 --- a/src/routes/user/+server.js +++ b/src/routes/user/+server.js @@ -1,5 +1,4 @@ import { auth, post } from '$lib/utils'; -import cookie from 'cookie'; export async function PUT({ cookies, request }) { let user = await request.json(); diff --git a/static/icons/party.svg b/static/icons/party.svg new file mode 100644 index 00000000..f33b9fc1 --- /dev/null +++ b/static/icons/party.svg @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="0 0 300.948 300.948" style="enable-background:new 0 0 300.948 300.948;" xml:space="preserve"> +<g id="XMLID_1505_"> + <g> + <g> + <path d="M132.166,219.612L120.7,189.007l-18.92,25.645l4.57,12.2l1.089,52.22c0.152,7.28,6.101,13.079,13.349,13.079 + c0.094,0,0.189-0.001,0.284-0.003c7.375-0.154,13.229-6.257,13.076-13.632l-1.137-54.496 + C132.98,222.513,132.694,221.023,132.166,219.612z"/> + <circle cx="92.165" cy="50.896" r="23.069"/> + <path d="M170.846,40.209c-4.878-3.74-11.866-2.818-15.606,2.061l-29.253,38.153l-12.6,2.504c-2.239-1.26-4.819-1.987-7.57-1.997 + l-27.79-0.113c-8.606-0.035-15.612,6.914-15.647,15.521l-0.041,10.148c5.894-4.85,10.2-8.395,16.342-13.449l-27.839,36.752 + c-2.212,3.006-2.77,6.913-1.494,10.412l15.522,42.623c2.131,5.848,8.593,8.715,14.271,6.644c5.774-2.105,8.75-8.493,6.644-14.271 + l-13.189-36.173l25.099-34.09l-1.65,4.137c-2.548,6.388-9.458,23.716-12.013,30.124c9.171,25.153,8.43,23.12,11.792,32.342 + c4.13,11.328-1.694,23.839-13.023,27.969c-5.156,1.88-10.876,1.768-16.015-0.416l-6.946,9.415L7.481,234.98 + c-6.635,3.253-9.357,11.259-6.115,17.872c3.249,6.627,11.25,9.361,17.872,6.115l55.291-27.913 + c1.927-0.944,3.596-2.338,4.87-4.064l39.4-53.405c2.39-3.245,2.291-6.351,2.272-10.854l0.238-58.684l13.132-2.61 + c2.651-0.527,5.019-2,6.663-4.145l31.803-41.48C176.646,50.936,175.724,43.949,170.846,40.209z"/> + <path d="M250.435,216.627c-2.072,3.485-1.699,6.841-1.363,9.642l6.507,54.118c0.881,7.325,7.533,12.547,14.856,11.667 + c7.324-0.88,12.547-7.532,11.667-14.856l-6.236-51.857l7.302-32.973L250.435,216.627z"/> + <circle cx="255.17" cy="49.131" r="23.069"/> + <path d="M300.801,157.11c-0.001-0.005-0.001-0.009-0.002-0.013l-9.775-67.144c-1.24-8.517-9.15-14.416-17.666-13.176 + l-27.433,3.994l-25.751-17.246l-12.368-46.457c-1.582-5.94-7.68-9.474-13.62-7.892c-5.94,1.581-9.474,7.679-7.892,13.62 + l13.447,50.51c0.696,2.612,2.317,4.881,4.563,6.385l25.379,16.996c29.946-8.374,25.085-7.015,34.511-9.651l-36.571,21.323 + l-34.561-16.97c-5.132-2.52-11.407-0.729-14.403,4.194c-3.469,5.696-1.193,12.949,4.591,15.789 + c44.139,21.651,40.969,20.131,41.842,20.447c3.002,1.088,6.429,0.864,9.395-0.846l39.51-23.763l-35.156,33.548l3.577,24.567 + l18.423-2.959l-43.669,32.365c-1.723,1.278-3.113,2.95-4.053,4.879l-27.793,55.352c-3.232,6.631-0.477,14.626,6.154,17.859 + c6.617,3.225,14.619,0.49,17.859-6.154l26.362-52.417c44.648-33.09,34.888-25.947,59.437-43.736 + C299.365,167.448,301.554,162.275,300.801,157.11z"/> + </g> + </g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +<g> +</g> +</svg> diff --git a/static/images/loft.jpg b/static/images/loft.jpg Binary files differnew file mode 100644 index 00000000..39ca49ab --- /dev/null +++ b/static/images/loft.jpg diff --git a/static/images/logo.png b/static/images/logo.png Binary files differnew file mode 100644 index 00000000..6503a7d2 --- /dev/null +++ b/static/images/logo.png diff --git a/static/tickets/01.png b/static/tickets/01.png Binary files differnew file mode 100644 index 00000000..555904c0 --- /dev/null +++ b/static/tickets/01.png diff --git a/static/tickets/02.png b/static/tickets/02.png Binary files differnew file mode 100644 index 00000000..65d35ba2 --- /dev/null +++ b/static/tickets/02.png diff --git a/static/tickets/03.png b/static/tickets/03.png Binary files differnew file mode 100644 index 00000000..b073cef5 --- /dev/null +++ b/static/tickets/03.png diff --git a/static/tickets/04.png b/static/tickets/04.png Binary files differnew file mode 100644 index 00000000..14d6b567 --- /dev/null +++ b/static/tickets/04.png diff --git a/static/tickets/05.png b/static/tickets/05.png Binary files differnew file mode 100644 index 00000000..99351b3d --- /dev/null +++ b/static/tickets/05.png diff --git a/static/tickets/06.png b/static/tickets/06.png Binary files differnew file mode 100644 index 00000000..366747ac --- /dev/null +++ b/static/tickets/06.png diff --git a/static/tickets/07.png b/static/tickets/07.png Binary files differnew file mode 100644 index 00000000..f774c683 --- /dev/null +++ b/static/tickets/07.png diff --git a/static/tickets/08.png b/static/tickets/08.png Binary files differnew file mode 100644 index 00000000..4f8ad5c3 --- /dev/null +++ b/static/tickets/08.png diff --git a/static/tickets/09.png b/static/tickets/09.png Binary files differnew file mode 100644 index 00000000..f8fd9cf6 --- /dev/null +++ b/static/tickets/09.png diff --git a/static/tickets/10.png b/static/tickets/10.png Binary files differnew file mode 100644 index 00000000..d557099c --- /dev/null +++ b/static/tickets/10.png diff --git a/static/tickets/11.png b/static/tickets/11.png Binary files differnew file mode 100644 index 00000000..484150a6 --- /dev/null +++ b/static/tickets/11.png diff --git a/static/tickets/12.png b/static/tickets/12.png Binary files differnew file mode 100644 index 00000000..05b4a337 --- /dev/null +++ b/static/tickets/12.png diff --git a/static/tickets/13.png b/static/tickets/13.png Binary files differnew file mode 100644 index 00000000..a853d777 --- /dev/null +++ b/static/tickets/13.png diff --git a/static/tickets/14.png b/static/tickets/14.png Binary files differnew file mode 100644 index 00000000..3ad8105a --- /dev/null +++ b/static/tickets/14.png diff --git a/static/tickets/15.png b/static/tickets/15.png Binary files differnew file mode 100644 index 00000000..d34eed52 --- /dev/null +++ b/static/tickets/15.png diff --git a/static/tickets/16.png b/static/tickets/16.png Binary files differnew file mode 100644 index 00000000..8a1b15b6 --- /dev/null +++ b/static/tickets/16.png diff --git a/static/tickets/17.png b/static/tickets/17.png Binary files differnew file mode 100644 index 00000000..a763b6c9 --- /dev/null +++ b/static/tickets/17.png diff --git a/static/tickets/18.png b/static/tickets/18.png Binary files differnew file mode 100644 index 00000000..14811954 --- /dev/null +++ b/static/tickets/18.png diff --git a/static/tickets/19.png b/static/tickets/19.png Binary files differnew file mode 100644 index 00000000..04c3915b --- /dev/null +++ b/static/tickets/19.png diff --git a/static/tickets/20.png b/static/tickets/20.png Binary files differnew file mode 100644 index 00000000..c42c311d --- /dev/null +++ b/static/tickets/20.png diff --git a/static/tickets/21.png b/static/tickets/21.png Binary files differnew file mode 100644 index 00000000..3863d45e --- /dev/null +++ b/static/tickets/21.png diff --git a/static/tickets/22.png b/static/tickets/22.png Binary files differnew file mode 100644 index 00000000..0e8cb413 --- /dev/null +++ b/static/tickets/22.png diff --git a/static/tickets/23.png b/static/tickets/23.png Binary files differnew file mode 100644 index 00000000..bac50642 --- /dev/null +++ b/static/tickets/23.png diff --git a/static/tickets/24.png b/static/tickets/24.png Binary files differnew file mode 100644 index 00000000..140abd01 --- /dev/null +++ b/static/tickets/24.png diff --git a/static/tickets/25.png b/static/tickets/25.png Binary files differnew file mode 100644 index 00000000..fb5d5b32 --- /dev/null +++ b/static/tickets/25.png diff --git a/static/tickets/26.png b/static/tickets/26.png Binary files differnew file mode 100644 index 00000000..980f824f --- /dev/null +++ b/static/tickets/26.png diff --git a/static/tickets/27.png b/static/tickets/27.png Binary files differnew file mode 100644 index 00000000..6892cd7d --- /dev/null +++ b/static/tickets/27.png diff --git a/static/tickets/28.png b/static/tickets/28.png Binary files differnew file mode 100644 index 00000000..03f1b0fa --- /dev/null +++ b/static/tickets/28.png diff --git a/static/tickets/29.png b/static/tickets/29.png Binary files differnew file mode 100644 index 00000000..39a1eaff --- /dev/null +++ b/static/tickets/29.png diff --git a/static/tickets/30.png b/static/tickets/30.png Binary files differnew file mode 100644 index 00000000..35f7463a --- /dev/null +++ b/static/tickets/30.png diff --git a/static/tickets/31.png b/static/tickets/31.png Binary files differnew file mode 100644 index 00000000..2f7a6b2c --- /dev/null +++ b/static/tickets/31.png diff --git a/static/tickets/32.png b/static/tickets/32.png Binary files differnew file mode 100644 index 00000000..cae96a35 --- /dev/null +++ b/static/tickets/32.png diff --git a/static/tickets/33.png b/static/tickets/33.png Binary files differnew file mode 100644 index 00000000..1754a731 --- /dev/null +++ b/static/tickets/33.png diff --git a/static/tickets/34.png b/static/tickets/34.png Binary files differnew file mode 100644 index 00000000..4d3eb544 --- /dev/null +++ b/static/tickets/34.png diff --git a/static/tickets/35.png b/static/tickets/35.png Binary files differnew file mode 100644 index 00000000..be8775dc --- /dev/null +++ b/static/tickets/35.png diff --git a/static/tickets/36.png b/static/tickets/36.png Binary files differnew file mode 100644 index 00000000..a80c1b39 --- /dev/null +++ b/static/tickets/36.png diff --git a/static/tickets/37.png b/static/tickets/37.png Binary files differnew file mode 100644 index 00000000..a6536293 --- /dev/null +++ b/static/tickets/37.png diff --git a/static/tickets/38.png b/static/tickets/38.png Binary files differnew file mode 100644 index 00000000..06a22e8f --- /dev/null +++ b/static/tickets/38.png diff --git a/static/tickets/39.png b/static/tickets/39.png Binary files differnew file mode 100644 index 00000000..a45ce455 --- /dev/null +++ b/static/tickets/39.png diff --git a/static/tickets/40.png b/static/tickets/40.png Binary files differnew file mode 100644 index 00000000..acfd2a9a --- /dev/null +++ b/static/tickets/40.png diff --git a/static/tickets/41.png b/static/tickets/41.png Binary files differnew file mode 100644 index 00000000..5e98fa33 --- /dev/null +++ b/static/tickets/41.png diff --git a/static/tickets/42.png b/static/tickets/42.png Binary files differnew file mode 100644 index 00000000..eeefd71b --- /dev/null +++ b/static/tickets/42.png diff --git a/static/tickets/43.png b/static/tickets/43.png Binary files differnew file mode 100644 index 00000000..c1ce6eac --- /dev/null +++ b/static/tickets/43.png diff --git a/static/tickets/44.png b/static/tickets/44.png Binary files differnew file mode 100644 index 00000000..36b79835 --- /dev/null +++ b/static/tickets/44.png diff --git a/static/tickets/45.png b/static/tickets/45.png Binary files differnew file mode 100644 index 00000000..f1889622 --- /dev/null +++ b/static/tickets/45.png diff --git a/static/tickets/46.png b/static/tickets/46.png Binary files differnew file mode 100644 index 00000000..1a74008f --- /dev/null +++ b/static/tickets/46.png diff --git a/static/tickets/47.png b/static/tickets/47.png Binary files differnew file mode 100644 index 00000000..0d9bb11c --- /dev/null +++ b/static/tickets/47.png diff --git a/static/tickets/48.png b/static/tickets/48.png Binary files differnew file mode 100644 index 00000000..3f4c9b1f --- /dev/null +++ b/static/tickets/48.png diff --git a/static/tickets/49.png b/static/tickets/49.png Binary files differnew file mode 100644 index 00000000..0b88dc78 --- /dev/null +++ b/static/tickets/49.png diff --git a/static/tickets/50.png b/static/tickets/50.png Binary files differnew file mode 100644 index 00000000..bd2aa215 --- /dev/null +++ b/static/tickets/50.png |
