diff --git a/package-lock.json b/package-lock.json
index 23d47e9..522c60a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,8 +17,10 @@
"@sveltejs/adapter-static": "1.0.0-next.43",
"@sveltejs/kit": "1.0.0-next.489",
"@tailwindcss/typography": "^0.5.2",
+ "@types/qrcode-svg": "^1.1.1",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
+ "autoprefixer": "^10.4.13",
"ava": "^4.3.1",
"daisyui": "^2.0.2",
"eslint": "^7.32.0",
@@ -32,7 +34,7 @@
"svelte-check": "^2.0.0",
"svelte-preprocess": "^4.0.0",
"svelte-seo": "^1.2.1",
- "tailwindcss": "^3.0.22",
+ "tailwindcss": "^3.2.1",
"ts-node": "^10.4.0",
"tsconfig-paths": "^3.12.0",
"tslib": "^2.0.0",
@@ -637,6 +639,12 @@
"integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==",
"dev": true
},
+ "node_modules/@types/qrcode-svg": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/qrcode-svg/-/qrcode-svg-1.1.1.tgz",
+ "integrity": "sha512-uTuEgFXMknpun//Jj6b1R8T8LiMi9fNpH+cnhZr4b7col2HHTMmjYfm/WOZ7nzjuGpk+oTrpHhePe1qlWtHWTA==",
+ "dev": true
+ },
"node_modules/@types/sass": {
"version": "1.43.1",
"integrity": "sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==",
@@ -1010,8 +1018,9 @@
}
},
"node_modules/autoprefixer": {
- "version": "10.4.7",
- "integrity": "sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==",
+ "version": "10.4.13",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
+ "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==",
"dev": true,
"funding": [
{
@@ -1023,10 +1032,9 @@
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
}
],
- "peer": true,
"dependencies": {
- "browserslist": "^4.20.3",
- "caniuse-lite": "^1.0.30001335",
+ "browserslist": "^4.21.4",
+ "caniuse-lite": "^1.0.30001426",
"fraction.js": "^4.2.0",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
@@ -1371,8 +1379,9 @@
}
},
"node_modules/browserslist": {
- "version": "4.21.2",
- "integrity": "sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==",
+ "version": "4.21.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
+ "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
"dev": true,
"funding": [
{
@@ -1384,12 +1393,11 @@
"url": "https://tidelift.com/funding/github/npm/browserslist"
}
],
- "peer": true,
"dependencies": {
- "caniuse-lite": "^1.0.30001366",
- "electron-to-chromium": "^1.4.188",
+ "caniuse-lite": "^1.0.30001400",
+ "electron-to-chromium": "^1.4.251",
"node-releases": "^2.0.6",
- "update-browserslist-db": "^1.0.4"
+ "update-browserslist-db": "^1.0.9"
},
"bin": {
"browserslist": "cli.js"
@@ -1454,8 +1462,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001366",
- "integrity": "sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==",
+ "version": "1.0.30001429",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz",
+ "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==",
"dev": true,
"funding": [
{
@@ -1466,8 +1475,7 @@
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
}
- ],
- "peer": true
+ ]
},
"node_modules/catering": {
"version": "2.1.1",
@@ -2087,10 +2095,10 @@
"dev": true
},
"node_modules/electron-to-chromium": {
- "version": "1.4.189",
- "integrity": "sha512-dQ6Zn4ll2NofGtxPXaDfY2laIa6NyCQdqXYHdwH90GJQW0LpJJib0ZU/ERtbb0XkBEmUD2eJtagbOie3pdMiPg==",
- "dev": true,
- "peer": true
+ "version": "1.4.284",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
+ "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
+ "dev": true
},
"node_modules/emittery": {
"version": "0.11.0",
@@ -2768,8 +2776,9 @@
"integrity": "sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g=="
},
"node_modules/fast-glob": {
- "version": "3.2.11",
- "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
+ "version": "3.2.12",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+ "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -2945,7 +2954,6 @@
"version": "4.2.0",
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
"dev": true,
- "peer": true,
"engines": {
"node": "*"
},
@@ -4348,9 +4356,9 @@
},
"node_modules/node-releases": {
"version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
- "dev": true,
- "peer": true
+ "dev": true
},
"node_modules/nofilter": {
"version": "3.1.0",
@@ -4373,7 +4381,6 @@
"version": "0.1.2",
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
"dev": true,
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -4622,9 +4629,9 @@
}
},
"node_modules/postcss": {
- "version": "8.4.16",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
- "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
+ "version": "8.4.18",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
+ "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
"dev": true,
"funding": [
{
@@ -4708,11 +4715,12 @@
}
},
"node_modules/postcss-nested": {
- "version": "5.0.6",
- "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz",
+ "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==",
"dev": true,
"dependencies": {
- "postcss-selector-parser": "^6.0.6"
+ "postcss-selector-parser": "^6.0.10"
},
"engines": {
"node": ">=12.0"
@@ -4727,6 +4735,7 @@
},
"node_modules/postcss-selector-parser": {
"version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"dependencies": {
@@ -5655,8 +5664,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "3.1.6",
- "integrity": "sha512-7skAOY56erZAFQssT1xkpk+kWt2NrO45kORlxFPXUt3CiGsVPhH1smuH5XoDH6sGPXLyBv+zgCKA2HWBsgCytg==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.1.tgz",
+ "integrity": "sha512-Uw+GVSxp5CM48krnjHObqoOwlCt5Qo6nw1jlCRwfGy68dSYb/LwS9ZFidYGRiM+w6rMawkZiu1mEMAsHYAfoLg==",
"dev": true,
"dependencies": {
"arg": "^5.0.2",
@@ -5665,18 +5675,19 @@
"detective": "^5.2.1",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
- "fast-glob": "^3.2.11",
+ "fast-glob": "^3.2.12",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
- "lilconfig": "^2.0.5",
+ "lilconfig": "^2.0.6",
+ "micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.0.0",
- "postcss": "^8.4.14",
+ "postcss": "^8.4.17",
"postcss-import": "^14.1.0",
"postcss-js": "^4.0.0",
"postcss-load-config": "^3.1.4",
- "postcss-nested": "5.0.6",
+ "postcss-nested": "6.0.0",
"postcss-selector-parser": "^6.0.10",
"postcss-value-parser": "^4.2.0",
"quick-lru": "^5.1.1",
@@ -5912,8 +5923,9 @@
}
},
"node_modules/update-browserslist-db": {
- "version": "1.0.4",
- "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==",
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
"dev": true,
"funding": [
{
@@ -5925,7 +5937,6 @@
"url": "https://tidelift.com/funding/github/npm/browserslist"
}
],
- "peer": true,
"dependencies": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0"
@@ -5947,6 +5958,7 @@
},
"node_modules/util-deprecate": {
"version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
@@ -6694,6 +6706,12 @@
"integrity": "sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==",
"dev": true
},
+ "@types/qrcode-svg": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@types/qrcode-svg/-/qrcode-svg-1.1.1.tgz",
+ "integrity": "sha512-uTuEgFXMknpun//Jj6b1R8T8LiMi9fNpH+cnhZr4b7col2HHTMmjYfm/WOZ7nzjuGpk+oTrpHhePe1qlWtHWTA==",
+ "dev": true
+ },
"@types/sass": {
"version": "1.43.1",
"integrity": "sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==",
@@ -6925,13 +6943,13 @@
"dev": true
},
"autoprefixer": {
- "version": "10.4.7",
- "integrity": "sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA==",
+ "version": "10.4.13",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
+ "integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==",
"dev": true,
- "peer": true,
"requires": {
- "browserslist": "^4.20.3",
- "caniuse-lite": "^1.0.30001335",
+ "browserslist": "^4.21.4",
+ "caniuse-lite": "^1.0.30001426",
"fraction.js": "^4.2.0",
"normalize-range": "^0.1.2",
"picocolors": "^1.0.0",
@@ -7163,15 +7181,15 @@
}
},
"browserslist": {
- "version": "4.21.2",
- "integrity": "sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==",
+ "version": "4.21.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
+ "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
"dev": true,
- "peer": true,
"requires": {
- "caniuse-lite": "^1.0.30001366",
- "electron-to-chromium": "^1.4.188",
+ "caniuse-lite": "^1.0.30001400",
+ "electron-to-chromium": "^1.4.251",
"node-releases": "^2.0.6",
- "update-browserslist-db": "^1.0.4"
+ "update-browserslist-db": "^1.0.9"
}
},
"buffer": {
@@ -7204,10 +7222,10 @@
"dev": true
},
"caniuse-lite": {
- "version": "1.0.30001366",
- "integrity": "sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==",
- "dev": true,
- "peer": true
+ "version": "1.0.30001429",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz",
+ "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==",
+ "dev": true
},
"catering": {
"version": "2.1.1",
@@ -7661,10 +7679,10 @@
"dev": true
},
"electron-to-chromium": {
- "version": "1.4.189",
- "integrity": "sha512-dQ6Zn4ll2NofGtxPXaDfY2laIa6NyCQdqXYHdwH90GJQW0LpJJib0ZU/ERtbb0XkBEmUD2eJtagbOie3pdMiPg==",
- "dev": true,
- "peer": true
+ "version": "1.4.284",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
+ "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
+ "dev": true
},
"emittery": {
"version": "0.11.0",
@@ -8069,8 +8087,9 @@
"integrity": "sha512-Kl29QoNbNvn4nhDsLYjyIAaIqaJB6rBx5p3sL9VjaefJ+eMFBWVZiaoguaoZfzEKr5RhAti0UgM8703akGPJ6g=="
},
"fast-glob": {
- "version": "3.2.11",
- "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==",
+ "version": "3.2.12",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+ "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
@@ -8201,8 +8220,7 @@
"fraction.js": {
"version": "4.2.0",
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
- "dev": true,
- "peer": true
+ "dev": true
},
"fs.realpath": {
"version": "1.0.0",
@@ -9238,9 +9256,9 @@
},
"node-releases": {
"version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
"integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==",
- "dev": true,
- "peer": true
+ "dev": true
},
"nofilter": {
"version": "3.1.0",
@@ -9256,8 +9274,7 @@
"normalize-range": {
"version": "0.1.2",
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
- "dev": true,
- "peer": true
+ "dev": true
},
"object-hash": {
"version": "3.0.0",
@@ -9418,9 +9435,9 @@
}
},
"postcss": {
- "version": "8.4.16",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
- "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==",
+ "version": "8.4.18",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.18.tgz",
+ "integrity": "sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==",
"dev": true,
"requires": {
"nanoid": "^3.3.4",
@@ -9456,15 +9473,17 @@
}
},
"postcss-nested": {
- "version": "5.0.6",
- "integrity": "sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA==",
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.0.tgz",
+ "integrity": "sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==",
"dev": true,
"requires": {
- "postcss-selector-parser": "^6.0.6"
+ "postcss-selector-parser": "^6.0.10"
}
},
"postcss-selector-parser": {
"version": "6.0.10",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
"integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
"dev": true,
"requires": {
@@ -10072,8 +10091,9 @@
}
},
"tailwindcss": {
- "version": "3.1.6",
- "integrity": "sha512-7skAOY56erZAFQssT1xkpk+kWt2NrO45kORlxFPXUt3CiGsVPhH1smuH5XoDH6sGPXLyBv+zgCKA2HWBsgCytg==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.2.1.tgz",
+ "integrity": "sha512-Uw+GVSxp5CM48krnjHObqoOwlCt5Qo6nw1jlCRwfGy68dSYb/LwS9ZFidYGRiM+w6rMawkZiu1mEMAsHYAfoLg==",
"dev": true,
"requires": {
"arg": "^5.0.2",
@@ -10082,18 +10102,19 @@
"detective": "^5.2.1",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
- "fast-glob": "^3.2.11",
+ "fast-glob": "^3.2.12",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
- "lilconfig": "^2.0.5",
+ "lilconfig": "^2.0.6",
+ "micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.0.0",
- "postcss": "^8.4.14",
+ "postcss": "^8.4.17",
"postcss-import": "^14.1.0",
"postcss-js": "^4.0.0",
"postcss-load-config": "^3.1.4",
- "postcss-nested": "5.0.6",
+ "postcss-nested": "6.0.0",
"postcss-selector-parser": "^6.0.10",
"postcss-value-parser": "^4.2.0",
"quick-lru": "^5.1.1",
@@ -10256,10 +10277,10 @@
"integrity": "sha512-c8HsD3IbwmjjbLvoZuRI26TZic+TSEe8FPMLLOkN1AfYRhdjnKBU6yL+IwcSCbdZiX4e5t0lfMDLDCqj4Sq70g=="
},
"update-browserslist-db": {
- "version": "1.0.4",
- "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==",
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
"dev": true,
- "peer": true,
"requires": {
"escalade": "^3.1.1",
"picocolors": "^1.0.0"
@@ -10275,6 +10296,7 @@
},
"util-deprecate": {
"version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true
},
diff --git a/package.json b/package.json
index af3b408..938ac8c 100644
--- a/package.json
+++ b/package.json
@@ -15,8 +15,10 @@
"@sveltejs/adapter-static": "1.0.0-next.43",
"@sveltejs/kit": "1.0.0-next.489",
"@tailwindcss/typography": "^0.5.2",
+ "@types/qrcode-svg": "^1.1.1",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
+ "autoprefixer": "^10.4.13",
"ava": "^4.3.1",
"daisyui": "^2.0.2",
"eslint": "^7.32.0",
@@ -30,7 +32,7 @@
"svelte-check": "^2.0.0",
"svelte-preprocess": "^4.0.0",
"svelte-seo": "^1.2.1",
- "tailwindcss": "^3.0.22",
+ "tailwindcss": "^3.2.1",
"ts-node": "^10.4.0",
"tsconfig-paths": "^3.12.0",
"tslib": "^2.0.0",
diff --git a/postcss.config.cjs b/postcss.config.cjs
index 4408392..fdc8a49 100644
--- a/postcss.config.cjs
+++ b/postcss.config.cjs
@@ -1,5 +1,3 @@
module.exports = {
- plugins: {
- tailwindcss: {},
- },
-};
\ No newline at end of file
+ plugins: [require('tailwindcss'), require('autoprefixer')]
+}
diff --git a/src/components/Footer.svelte b/src/components/Footer.svelte
new file mode 100644
index 0000000..d7f555b
--- /dev/null
+++ b/src/components/Footer.svelte
@@ -0,0 +1,30 @@
+
+
+
+ {#if $themeStore === 'light'}
+
+ *** Experimental *** - You are currently previewing Webnative SDK Alpha
+ 0.2
+
+ {:else}
+
+ *** Experimental *** - You are currently previewing Webnative SDK Alpha
+ 0.2
+
+ {/if}
+
diff --git a/src/components/Header.svelte b/src/components/Header.svelte
index 7af49f0..3564a9b 100644
--- a/src/components/Header.svelte
+++ b/src/components/Header.svelte
@@ -1,11 +1,14 @@
-
- goto('/')}>
-
-
{appName}
+
+
+ {#if $sessionStore.authed}
+
+
+
+ {:else}
+
+ {/if}
- {#if !$sessionStore.loading && !$sessionStore.authed}
-
-
- Connect
-
+
+ {#if !$sessionStore.authed || $page.url.pathname.match(/register|backup|delegate/)}
+
{/if}
- {#if !$sessionStore.loading && $sessionStore.backupCreated === false}
-
goto('/delegate-account')}
- class="btn btn-sm h-10 btn-warning rounded-full font-normal transition-colors ease-in hover:bg-orange-500 hover:border-orange-500"
- >
-
- Backup recommended
-
- {/if}
+
+ {#if !$sessionStore.loading && !$sessionStore.authed}
+
+ {/if}
-
- {#if $themeStore === 'light'}
- setTheme('dark')}>
-
-
- {:else}
- setTheme('light')}>
-
+ {#if !$sessionStore.loading && $sessionStore.backupCreated === false}
+ goto('/delegate-account')}
+ class="btn btn-sm h-10 btn-warning rounded-full bg-orange-300 border-2 border-neutral font-semiBold text-neutral transition-colors ease-in hover:bg-orange-300"
+ >
+ Backup recommended
+
{/if}
-
+
+ {#if $sessionStore.authed}
+
+
+
+ {/if}
+
+
+ {#if $themeStore === 'light'}
+ setTheme('dark')}>
+
+
+ {:else}
+ setTheme('light')}>
+
+
+ {/if}
+
+
diff --git a/src/components/about/AboutThisTemplate.svelte b/src/components/about/AboutThisTemplate.svelte
new file mode 100644
index 0000000..35e67f1
--- /dev/null
+++ b/src/components/about/AboutThisTemplate.svelte
@@ -0,0 +1,40 @@
+
+
About This Template
+
+
+
diff --git a/src/components/auth/backup/AreYouSure.svelte b/src/components/auth/backup/AreYouSure.svelte
index 99efe45..8eea03b 100644
--- a/src/components/auth/backup/AreYouSure.svelte
+++ b/src/components/auth/backup/AreYouSure.svelte
@@ -18,16 +18,15 @@
-
+
-
Are you sure?
+
Are you sure?
-
+
Without a backup device, if you lose this device or reset your browser,
you will not be able to recover your account data.
+
goto('/delegate-account')}
@@ -35,7 +34,7 @@
Connect a backup device
YOLO—I'll risk just one device for now
diff --git a/src/components/auth/backup/Backup.svelte b/src/components/auth/backup/Backup.svelte
index c78a14e..0527306 100644
--- a/src/components/auth/backup/Backup.svelte
+++ b/src/components/auth/backup/Backup.svelte
@@ -14,18 +14,16 @@
-
+
-
Backup your account
-
+
Backup your account
+
Your {appName} account & its data live only on your devices.
-
- We highly recommend connecting your account on at least one more device,
- so that you have a backup.
+
+ We highly recommend backing up your account on at least one additional
+ device.
navigate('are-you-sure')}
>
Skip for now
diff --git a/src/components/auth/connect/Connect.svelte b/src/components/auth/connect/Connect.svelte
index 6a2c713..fd39895 100644
--- a/src/components/auth/connect/Connect.svelte
+++ b/src/components/auth/connect/Connect.svelte
@@ -13,24 +13,17 @@
-
-
- ✕
-
+
+
✕
-
Connect to {appName}
+
Connect to {appName}
Create a new account
navigate('open-connected-device')}
>
I have an existing account
diff --git a/src/components/auth/connect/OpenConnectedDevice.svelte b/src/components/auth/connect/OpenConnectedDevice.svelte
index bb62441..c46bb7a 100644
--- a/src/components/auth/connect/OpenConnectedDevice.svelte
+++ b/src/components/auth/connect/OpenConnectedDevice.svelte
@@ -5,18 +5,18 @@
class="modal-toggle"
/>
-
+
✕
-
Connect an existing account
+
Connect your existing account
-
- To connect with an existing account, you'll need a device that's
- already connected to that account.
+
+ To connect your existing account on this device, you’ll need a device
+ you are already connected on.
-
- On that device, click "Connect a new device" and follow the
- instuctions.
+
+ On that device, click “Connect a new device” and follow the
+ instructions.
diff --git a/src/components/auth/delegate-account/ConnectBackupDevice.svelte b/src/components/auth/delegate-account/ConnectBackupDevice.svelte
index de4721c..1f73c02 100644
--- a/src/components/auth/delegate-account/ConnectBackupDevice.svelte
+++ b/src/components/auth/delegate-account/ConnectBackupDevice.svelte
@@ -2,39 +2,48 @@
import { goto } from '$app/navigation'
import clipboardCopy from 'clipboard-copy'
- import ClipboardIcon from '$components/icons/ClipboardIcon.svelte'
+ import Share from '$components/icons/Share.svelte'
+ import { addNotification } from '$lib/notifications'
- export let qrcode
+ export let qrcode: HTMLOrSVGElement
export let connectionLink: string
export let backupCreated: boolean
const copyLink = async () => {
await clipboardCopy(connectionLink)
+ addNotification('Copied to clipboard', 'success')
}
-
+
-
Connect a backup device
- {@html qrcode}
-
+
Connect a backup device
+
+ {@html qrcode}
+
+
Scan this code on the new device, or share the connection link.
-
-
- Copy connection link
+
+
+ Share connection link
{#if !backupCreated}
goto('/backup?view=are-you-sure')}
>
Skip for now
+ {:else}
+
+ Cancel
+
{/if}
diff --git a/src/components/auth/delegate-account/DelegateAccount.svelte b/src/components/auth/delegate-account/DelegateAccount.svelte
index 2edad97..ff5d236 100644
--- a/src/components/auth/delegate-account/DelegateAccount.svelte
+++ b/src/components/auth/delegate-account/DelegateAccount.svelte
@@ -10,8 +10,16 @@
dispatch('cancel')
}
+ /**
+ * Auto submit the form when the pinInput is equal to the TARGET_PIN_LENGTH
+ */
+ const TARGET_PIN_LENGTH = 6
const checkPin = () => {
- dispatch('checkpin')
+ if (pinInput.length === TARGET_PIN_LENGTH) {
+ dispatch('checkpin')
+ } else {
+ pinError = false
+ }
}
@@ -22,24 +30,27 @@
class="modal-toggle"
/>
-
+
-
+
A new device would like to connect to your account
{#if !pinError}
-
- Enter the connection code to approve the connection.
+
+ Enter the connection code from that device to approve this
+ connection.
{:else}
@@ -49,13 +60,7 @@
-
- Approve the connection
-
-
+
Cancel Request
diff --git a/src/components/auth/link-device/LinkDevice.svelte b/src/components/auth/link-device/LinkDevice.svelte
index 2c94a11..5cff4c4 100644
--- a/src/components/auth/link-device/LinkDevice.svelte
+++ b/src/components/auth/link-device/LinkDevice.svelte
@@ -1,5 +1,9 @@
-
Connection Requested
+
Connect to {appName}
{#if pin}
{pin}
{/if}
-
Enter this code on your connected device.
+
+ Enter this code on your connected device.
+
@@ -37,7 +49,7 @@
Cancel Request
diff --git a/src/components/auth/register/Register.svelte b/src/components/auth/register/Register.svelte
index fce149f..869ec51 100644
--- a/src/components/auth/register/Register.svelte
+++ b/src/components/auth/register/Register.svelte
@@ -32,6 +32,10 @@
}
const registerUser = async () => {
+ if (checkingUsername) {
+ return
+ }
+
initializingFilesystem = true
registrationSuccess = await register(username)
@@ -45,40 +49,33 @@
{:else}
-
-
- ✕
-
+
+
✕
-
Choose a username
+
Choose a username
{#if checkingUsername}
{/if}
{#if !(username.length === 0) && usernameAvailable && usernameValid && !checkingUsername}
-
+
{/if}
{#if !(username.length === 0) && !checkingUsername && !(usernameAvailable && usernameValid)}
-
+
{/if}
@@ -88,16 +85,16 @@
{#if usernameValid && usernameAvailable}
-
- The username is available.
+
+ This username is available.
{:else if !usernameValid}
- The username is invalid.
+ This username is invalid.
{:else if !usernameAvailable}
- The username is unavailable.
+ This username is unavailable.
{/if}
@@ -115,7 +112,7 @@
-
Back
+
Back
Register
diff --git a/src/components/auth/register/Welcome.svelte b/src/components/auth/register/Welcome.svelte
index 141c944..d5191f8 100644
--- a/src/components/auth/register/Welcome.svelte
+++ b/src/components/auth/register/Welcome.svelte
@@ -6,22 +6,18 @@
-
+
-
+
Welcome, {$sessionStore.username}!
-
-
-
-
+
+
-
Your account has been created.
+
Your account has been created.
-
+
-
+
{activity} file system...
diff --git a/src/components/common/LoadingSpinner.svelte b/src/components/common/LoadingSpinner.svelte
deleted file mode 100644
index e371470..0000000
--- a/src/components/common/LoadingSpinner.svelte
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
diff --git a/src/components/home/Authed.svelte b/src/components/home/Authed.svelte
new file mode 100644
index 0000000..7b1dca1
--- /dev/null
+++ b/src/components/home/Authed.svelte
@@ -0,0 +1,31 @@
+
+
+
+
Welcome, {$sessionStore.username}!
+
+
+
Photo Gallery Demo
+
+ Webnative makes it easy to implement private, encrypted, user-owned
+ storage in your app. See it in action with our photo gallery demo.
+
+
Try the Photo Gallery Demo
+
+
+
+
Device Connection Demo
+
+ With Webnative SDK, a user’s account lives only on their connected devices
+ — entirely under their control. It’s easy for them to connect as many
+ devices as they’d like. For recoverability, we recommend they always
+ connect at least two.
+
+
+ Connect an additional device
+
+
+
diff --git a/src/components/home/Public.svelte b/src/components/home/Public.svelte
new file mode 100644
index 0000000..d179874
--- /dev/null
+++ b/src/components/home/Public.svelte
@@ -0,0 +1,33 @@
+
+
+
+
Welcome to the {appName}
+
+
+
+ Webnative SDK is a true local-first edge computing stack. Effortlessly
+ give your users:
+
+
+
+
+ modern, passwordless accounts
+ , without a complex and costly cloud-native back-end
+
+
+ user-controlled data
+ , secured by default with our encrypted-at-rest file storage protocol
+
+
+ local-first functionality
+ , including the ability to work offline and collaborate across multiple devices
+
+
+
+
Connect
+
+
diff --git a/src/components/icons/About.svelte b/src/components/icons/About.svelte
new file mode 100644
index 0000000..4aaca17
--- /dev/null
+++ b/src/components/icons/About.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/icons/Brand.svelte b/src/components/icons/Brand.svelte
deleted file mode 100644
index 484f1a4..0000000
--- a/src/components/icons/Brand.svelte
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
diff --git a/src/components/icons/BrandLogo.svelte b/src/components/icons/BrandLogo.svelte
new file mode 100644
index 0000000..f9bdee3
--- /dev/null
+++ b/src/components/icons/BrandLogo.svelte
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/components/icons/BrandWordmark.svelte b/src/components/icons/BrandWordmark.svelte
new file mode 100644
index 0000000..75b97d7
--- /dev/null
+++ b/src/components/icons/BrandWordmark.svelte
@@ -0,0 +1,8 @@
+
+
+
diff --git a/src/components/icons/DarkMode.svelte b/src/components/icons/DarkMode.svelte
index 35e3df6..16ebe8e 100644
--- a/src/components/icons/DarkMode.svelte
+++ b/src/components/icons/DarkMode.svelte
@@ -1,16 +1,19 @@
-
-
-
+
+
diff --git a/src/components/icons/Discord.svelte b/src/components/icons/Discord.svelte
new file mode 100644
index 0000000..714583d
--- /dev/null
+++ b/src/components/icons/Discord.svelte
@@ -0,0 +1,6 @@
+
+
+
diff --git a/src/components/icons/ExternalLink.svelte b/src/components/icons/ExternalLink.svelte
new file mode 100644
index 0000000..f1e6a56
--- /dev/null
+++ b/src/components/icons/ExternalLink.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/icons/Github.svelte b/src/components/icons/Github.svelte
new file mode 100644
index 0000000..e781231
--- /dev/null
+++ b/src/components/icons/Github.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/icons/Hamburger.svelte b/src/components/icons/Hamburger.svelte
new file mode 100644
index 0000000..b2d5423
--- /dev/null
+++ b/src/components/icons/Hamburger.svelte
@@ -0,0 +1,3 @@
+
+
+
diff --git a/src/components/icons/Home.svelte b/src/components/icons/Home.svelte
new file mode 100644
index 0000000..513189a
--- /dev/null
+++ b/src/components/icons/Home.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/icons/LightMode.svelte b/src/components/icons/LightMode.svelte
index 73dcfd9..06d148f 100644
--- a/src/components/icons/LightMode.svelte
+++ b/src/components/icons/LightMode.svelte
@@ -1,16 +1,19 @@
-
-
-
+
+
diff --git a/src/components/icons/PhotoGallery.svelte b/src/components/icons/PhotoGallery.svelte
new file mode 100644
index 0000000..e1efbef
--- /dev/null
+++ b/src/components/icons/PhotoGallery.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/icons/Settings.svelte b/src/components/icons/Settings.svelte
new file mode 100644
index 0000000..8c3c705
--- /dev/null
+++ b/src/components/icons/Settings.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/icons/Share.svelte b/src/components/icons/Share.svelte
new file mode 100644
index 0000000..16ffefa
--- /dev/null
+++ b/src/components/icons/Share.svelte
@@ -0,0 +1,9 @@
+
+
+
diff --git a/src/components/icons/Shield.svelte b/src/components/icons/Shield.svelte
index e7c5520..259a81e 100644
--- a/src/components/icons/Shield.svelte
+++ b/src/components/icons/Shield.svelte
@@ -1,15 +1,9 @@
-
+
diff --git a/src/components/icons/WelcomeCheckIcon.svelte b/src/components/icons/WelcomeCheckIcon.svelte
index abfdf62..7f0630b 100644
--- a/src/components/icons/WelcomeCheckIcon.svelte
+++ b/src/components/icons/WelcomeCheckIcon.svelte
@@ -1,14 +1,8 @@
-
+
diff --git a/src/components/nav/AlphaTag.svelte b/src/components/nav/AlphaTag.svelte
new file mode 100644
index 0000000..c3fd724
--- /dev/null
+++ b/src/components/nav/AlphaTag.svelte
@@ -0,0 +1,5 @@
+
+ ALPHA
+
diff --git a/src/components/nav/SidebarNav.svelte b/src/components/nav/SidebarNav.svelte
new file mode 100644
index 0000000..be17e6b
--- /dev/null
+++ b/src/components/nav/SidebarNav.svelte
@@ -0,0 +1,95 @@
+
+
+
+{#if $sessionStore.authed && !$page.url.pathname.match(/register|backup|delegate/)}
+
+{:else}
+
+{/if}
diff --git a/src/components/notifications/Notification.svelte b/src/components/notifications/Notification.svelte
index 92a8d6f..6f87e4c 100644
--- a/src/components/notifications/Notification.svelte
+++ b/src/components/notifications/Notification.svelte
@@ -26,7 +26,7 @@
success: {
component: CheckThinIcon,
props: {
- color: $themeStore === 'light' ? '#b8ffd3' : '#002e12'
+ color: '#14532D'
}
},
warning: {
diff --git a/src/components/notifications/Notifications.svelte b/src/components/notifications/Notifications.svelte
index 501509e..5e34885 100644
--- a/src/components/notifications/Notifications.svelte
+++ b/src/components/notifications/Notifications.svelte
@@ -6,7 +6,7 @@
{#if $notificationStore.length}
-
+
{#each $notificationStore as notification (notification.id)}
diff --git a/src/components/settings/Avatar.svelte b/src/components/settings/Avatar.svelte
new file mode 100644
index 0000000..58b24ba
--- /dev/null
+++ b/src/components/settings/Avatar.svelte
@@ -0,0 +1,40 @@
+
+
+{#if $accountSettingsStore.avatar}
+ {#if $accountSettingsStore.loading}
+
+
+
+ {:else}
+
+ {/if}
+{:else}
+
+ {$sessionStore.username[0]}
+
+{/if}
diff --git a/src/components/settings/AvatarUpload.svelte b/src/components/settings/AvatarUpload.svelte
new file mode 100644
index 0000000..b413870
--- /dev/null
+++ b/src/components/settings/AvatarUpload.svelte
@@ -0,0 +1,35 @@
+
+
+
Avatar
+
+
+
+
Upload a new avatar
+
+
diff --git a/src/components/settings/ThemePreferences.svelte b/src/components/settings/ThemePreferences.svelte
new file mode 100644
index 0000000..b736cff
--- /dev/null
+++ b/src/components/settings/ThemePreferences.svelte
@@ -0,0 +1,48 @@
+
+
+
Theme preference
+
+{#each options as option}
+
+
+
+ {option.label}
+
+
+{/each}
diff --git a/src/global.css b/src/global.css
index d7dbc06..d5d2376 100644
--- a/src/global.css
+++ b/src/global.css
@@ -2,10 +2,95 @@
@tailwind components;
@tailwind utilities;
-.modal-box {
- @apply p-8;
+@font-face {
+ font-family: 'UncutSans_Regular';
+ src: url('/fonts/uncut-sans-regular-webfont.woff2') format('woff2'),
+ url('/fonts/uncut-sans-regular-webfont.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
}
+@font-face {
+ font-family: 'UncutSans_Medium';
+ src: url('/fonts/uncut-sans-medium-webfont.woff2') format('woff2'),
+ url('/fonts/uncut-sans-medium-webfont.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'UncutSans_Bold';
+ src: url('/fonts/uncut-sans-bold-webfont.woff2') format('woff2'),
+ url('/fonts/uncut-sans-bold-webfont.woff') format('woff');
+ font-weight: normal;
+ font-style: normal;
+}
+
+h1, h2, h3 {
+ @apply font-bold;
+}
+
+h4, h5, h6 {
+ @apply font-semiBold;
+}
+
+body, p, li, a, span, input {
+ @apply font-sans;
+}
+
+/* Button default styles */
+.btn {
+ @apply font-semiBold;
+ @apply border-2;
+ @apply min-h-0;
+}
+
+.btn-circle {
+ @apply text-base-100;
+ @apply bg-base-content;
+}
+
+.btn-outline {
+ @apply text-sm;
+ @apply text-base-content;
+ @apply border-base-content;
+ @apply bg-base-100;
+ @apply shadow-orange;
+ @apply h-11;
+ @apply px-4;
+}
+
+.btn-primary {
+ @apply text-sm;
+ @apply text-neutral;
+ @apply border-neutral;
+ @apply shadow-orange;
+ @apply bg-gradient-to-r;
+ @apply from-orange-600;
+ @apply to-orange-300;
+ @apply h-11;
+ @apply px-4;
+}
+
+.btn-primary:hover, .btn-warning:hover {
+ @apply border-orange-300;
+}
+
+/* Input default styles */
+.input-bordered {
+ @apply text-base-content;
+ @apply border-2;
+ @apply border-base-content;
+}
+
+/* Modal default styles */
+.modal-box {
+ @apply p-8;
+ @apply border-2;
+ @apply border-base-content;
+}
+
+/* Label default styles */
.label {
@apply px-0;
}
diff --git a/src/lib/account-settings.ts b/src/lib/account-settings.ts
new file mode 100644
index 0000000..1027632
--- /dev/null
+++ b/src/lib/account-settings.ts
@@ -0,0 +1,180 @@
+import { get as getStore } from 'svelte/store'
+import * as wn from 'webnative'
+import * as uint8arrays from 'uint8arrays'
+import type { CID } from 'multiformats/cid'
+import type { PuttableUnixTree, File as WNFile } from 'webnative/fs/types'
+import type { Metadata } from 'webnative/fs/metadata'
+
+import { accountSettingsStore, filesystemStore } from '$src/stores'
+import { addNotification } from '$lib/notifications'
+
+export type Avatar = {
+ cid: string
+ ctime: number
+ name: string
+ size?: number
+ src: string
+}
+
+export type AccountSettings = {
+ avatar: Avatar
+ loading: boolean
+}
+interface AvatarFile extends PuttableUnixTree, WNFile {
+ cid: CID
+ content: Uint8Array
+ header: {
+ content: Uint8Array
+ metadata: Metadata
+ }
+}
+
+export const ACCOUNT_SETTINGS_DIR = ['private', 'settings']
+const AVATAR_DIR = [...ACCOUNT_SETTINGS_DIR, 'avatars']
+const AVATAR_ARCHIVE_DIR = [...AVATAR_DIR, 'archive']
+const AVATAR_FILE_NAME = 'avatar'
+const FILE_SIZE_LIMIT = 5
+
+/**
+ * Move old avatar to the archive directory
+ */
+const archiveOldAvatar = async (): Promise
=> {
+ const fs = getStore(filesystemStore)
+
+ // Return if user has not uploaded an avatar yet
+ const avatarDirExists = await fs.exists(wn.path.file(...AVATAR_DIR))
+ if (!avatarDirExists) {
+ return
+ }
+
+ // Find the filename of the old avatar
+ const path = wn.path.directory(...AVATAR_DIR)
+ const links = await fs.ls(path)
+ const oldAvatarFileName = Object.keys(links).find(key =>
+ key.includes(AVATAR_FILE_NAME)
+ )
+ const oldFileNameArray = oldAvatarFileName.split('.')[0]
+ const archiveFileName = `${oldFileNameArray[0]}-${Date.now()}.${
+ oldFileNameArray[1]
+ }`
+
+ // Move old avatar to archive dir
+ const fromPath = wn.path.file(...AVATAR_DIR, oldAvatarFileName)
+ const toPath = wn.path.file(...AVATAR_ARCHIVE_DIR, archiveFileName)
+ await fs.mv(fromPath, toPath)
+
+ // Announce the changes to the server
+ await fs.publish()
+}
+
+/**
+ * Get the Avatar from the user's WNFS and construct its `src`
+ */
+export const getAvatarFromWNFS = async (): Promise => {
+ try {
+ // Set loading: true on the accountSettingsStore
+ accountSettingsStore.update(store => ({ ...store, loading: true }))
+
+ const fs = getStore(filesystemStore)
+
+ // If the avatar dir doesn't exist, silently fail and let the UI handle it
+ const avatarDirExists = await fs.exists(wn.path.file(...AVATAR_DIR))
+ if (!avatarDirExists) {
+ accountSettingsStore.update(store => ({
+ ...store,
+ loading: false
+ }))
+ return
+ }
+
+ // Find the file that matches the AVATAR_FILE_NAME
+ const path = wn.path.directory(...AVATAR_DIR)
+ const links = await fs.ls(path)
+ const avatarName = Object.keys(links).find(key =>
+ key.includes(AVATAR_FILE_NAME)
+ )
+
+ // If user has not uploaded an avatar, silently fail and let the UI handle it
+ if (!avatarName) {
+ accountSettingsStore.update(store => ({
+ ...store,
+ loading: false
+ }))
+ return
+ }
+
+ const file = await fs.get(wn.path.file(...AVATAR_DIR, `${avatarName}`))
+
+ // The CID for private files is currently located in `file.header.content`
+ const cid = (file as AvatarFile).header.content.toString()
+
+ // Create a base64 string to use as the image `src`
+ const src = `data:image/jpeg;base64, ${uint8arrays.toString(
+ (file as AvatarFile).content,
+ 'base64'
+ )}`
+
+ const avatar = {
+ cid,
+ ctime: (file as AvatarFile).header.metadata.unixMeta.ctime,
+ name: avatarName,
+ src
+ }
+
+ // Push images to the accountSettingsStore
+ accountSettingsStore.update(store => ({
+ ...store,
+ avatar,
+ loading: false
+ }))
+ } catch (error) {
+ console.error(error)
+ accountSettingsStore.update(store => ({
+ ...store,
+ avatar: null,
+ loading: false
+ }))
+ }
+}
+
+/**
+ * Upload an avatar image to the user's private WNFS
+ * @param image
+ */
+export const uploadAvatarToWNFS = async (image: File): Promise => {
+ try {
+ // Set loading: true on the accountSettingsStore
+ accountSettingsStore.update(store => ({ ...store, loading: true }))
+
+ const fs = getStore(filesystemStore)
+
+ // Reject files over 5MB
+ const imageSizeInMB = image.size / (1024 * 1024)
+ if (imageSizeInMB > FILE_SIZE_LIMIT) {
+ throw new Error('Image can be no larger than 5MB')
+ }
+
+ // Archive old avatar
+ await archiveOldAvatar()
+
+ // Rename the file to `avatar.[extension]`
+ const updatedImage = new File(
+ [image],
+ `${AVATAR_FILE_NAME}.${image.name.split('.')[1]}`,
+ {
+ type: image.type
+ }
+ )
+
+ // Create a sub directory and add the avatar
+ await fs.write(wn.path.file(...AVATAR_DIR, updatedImage.name), updatedImage)
+
+ // Announce the changes to the server
+ await fs.publish()
+
+ addNotification(`Your avatar has been updated!`, 'success')
+ } catch (error) {
+ addNotification(error.message, 'error')
+ console.error(error)
+ }
+}
diff --git a/src/lib/app-info.ts b/src/lib/app-info.ts
index d2167cd..745716a 100644
--- a/src/lib/app-info.ts
+++ b/src/lib/app-info.ts
@@ -1,4 +1,4 @@
-export const appName = 'Awesome Webnative App'
+export const appName = 'Webnative SDK Demo'
export const appDescription = 'This is another awesome Webnative app.'
export const appURL = 'https://webnative.netlify.app'
export const appImageURL = `${appURL}/preview.png`
diff --git a/src/lib/auth/account.ts b/src/lib/auth/account.ts
index a43e6fd..5ef4f46 100644
--- a/src/lib/auth/account.ts
+++ b/src/lib/auth/account.ts
@@ -4,6 +4,7 @@ import type FileSystem from 'webnative/fs/index'
import { asyncDebounce } from '$lib/utils'
import { filesystemStore, sessionStore } from '../../stores'
import { getBackupStatus } from '$lib/auth/backup'
+import { ACCOUNT_SETTINGS_DIR } from '$lib/account-settings'
import { AREAS } from '$routes/gallery/stores'
import { GALLERY_DIRS } from '$routes/gallery/lib/gallery'
@@ -50,6 +51,7 @@ export const register = async (username: string): Promise => {
const initializeFilesystem = async (fs: FileSystem): Promise => {
await fs.mkdir(webnative.path.directory(...GALLERY_DIRS[AREAS.PUBLIC]))
await fs.mkdir(webnative.path.directory(...GALLERY_DIRS[AREAS.PRIVATE]))
+ await fs.mkdir(webnative.path.directory(...ACCOUNT_SETTINGS_DIR))
}
export const loadAccount = async (username: string): Promise => {
diff --git a/src/lib/theme.ts b/src/lib/theme.ts
index b7e1878..936ce00 100644
--- a/src/lib/theme.ts
+++ b/src/lib/theme.ts
@@ -1,13 +1,14 @@
import { browser } from '$app/environment'
-export type Theme = 'light' | 'dark'
+export type Theme = 'light' | 'dark' | 'default'
+
+export const getSystemDefaultTheme = (): Theme =>
+ window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
export const loadTheme = (): Theme => {
if (browser) {
const browserTheme = localStorage.getItem('theme') as Theme
- const osTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
- ? 'dark'
- : 'light'
+ const osTheme = getSystemDefaultTheme()
return browserTheme ?? (osTheme as Theme) ?? 'light'
}
diff --git a/src/routes/+error.svelte b/src/routes/+error.svelte
new file mode 100644
index 0000000..520a0b3
--- /dev/null
+++ b/src/routes/+error.svelte
@@ -0,0 +1,43 @@
+
+
+
+
404 - Page not found
+
+
The page you have requested does not exist.
+
+
+
+
+
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index baae8d9..968dd05 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -5,8 +5,10 @@
import { sessionStore, themeStore } from '../stores'
import { errorToMessage } from '$lib/session'
import { initialize } from '$lib/init'
+ import Footer from '$components/Footer.svelte'
import Header from '$components/Header.svelte'
import Notifications from '$components/notifications/Notifications.svelte'
+ import SidebarNav from '$components/nav/SidebarNav.svelte'
sessionStore.subscribe(session => {
if (session.error) {
@@ -51,7 +53,12 @@
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 5fd853e..20aa483 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,108 +1,11 @@
-
-
Welcome to {appName}!
-
- {#if session?.authed}
-
-
-
👋 Account
-
- Your username is
-
- {session.username}
-
-
-
- {#if session.backupCreated}
-
✅ You have connected your account on another device.
- {:else}
-
-
-
-
-
- You have not connected your account on another device.
-
- {/if}
-
- goto('/delegate-account')}
- >
- Connect a new device
-
-
-
-
-
-
-
-
📷 Photo Gallery Demo
-
- Try out the Webnative File System by storing your photos in public and
- private storage.
-
-
-
-
- {/if}
-
-
+{#if $sessionStore?.authed}
+
+{:else}
+
+{/if}
diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte
new file mode 100644
index 0000000..15e16d1
--- /dev/null
+++ b/src/routes/about/+page.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/src/routes/delegate-account/+page.svelte b/src/routes/delegate-account/+page.svelte
index 3e0173d..9c8ca31 100644
--- a/src/routes/delegate-account/+page.svelte
+++ b/src/routes/delegate-account/+page.svelte
@@ -48,8 +48,11 @@
connectionLink = `${origin}/link-device?username=${username}`
qrcode = new QRCode({
content: connectionLink,
- color: $themeStore === 'light' ? '#334155' : '#E2E8F0',
- background: '#ffffff00'
+ color: $themeStore === 'light' ? '#171717' : '#FAFAFA',
+ background: $themeStore === 'light' ? '#FAFAFA' : '#171717',
+ padding: 0,
+ width: 216,
+ height: 216
}).svg()
initAccountLinkingProducer(username)
diff --git a/src/routes/gallery/+page.svelte b/src/routes/gallery/+page.svelte
index 6a15909..4e1b329 100644
--- a/src/routes/gallery/+page.svelte
+++ b/src/routes/gallery/+page.svelte
@@ -2,7 +2,7 @@
import { onDestroy } from 'svelte'
import { goto } from '$app/navigation'
- import { sessionStore, themeStore } from '$src/stores'
+ import { sessionStore } from '$src/stores'
import { AREAS, galleryStore } from '$routes/gallery/stores'
import Dropzone from '$routes/gallery/components/upload/Dropzone.svelte'
import ImageGallery from '$routes/gallery/components/imageGallery/ImageGallery.svelte'
@@ -27,22 +27,19 @@
onDestroy(unsubscribe)
-
+
{#if $sessionStore.authed}
-