16 files changed,
268 insertions(+),
72 deletions(-)
Author:
Smirnov Oleksandr
ss2316544@gmail.com
Committed by:
GitHub
noreply@github.com
Committed at:
2025-06-19 19:46:27 +0300
Parent:
8ffca5c
jump to
M
web/bun.lock
··· 4 4 "": { 5 5 "name": "web", 6 6 "devDependencies": { 7 + "autoprefixer": "^10.4.21", 7 8 "elm-land": "^0.20.1", 8 9 "elm-review": "^2.13.2", 9 10 "elm-tooling": "^1.15.1", 11 + "missing.css": "^1.1.3", 12 + "postcss": "^8.5.6", 13 + "postcss-import": "^16.1.1", 10 14 }, 11 15 }, 12 16 }, ··· 157 161 158 162 "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], 159 163 164 + "autoprefixer": ["autoprefixer@10.4.21", "", { "dependencies": { "browserslist": "^4.24.4", "caniuse-lite": "^1.0.30001702", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.1.0" }, "bin": { "autoprefixer": "bin/autoprefixer" } }, "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ=="], 165 + 160 166 "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], 161 167 162 168 "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], ··· 169 175 170 176 "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], 171 177 178 + "browserslist": ["browserslist@4.25.0", "", { "dependencies": { "caniuse-lite": "^1.0.30001718", "electron-to-chromium": "^1.5.160", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA=="], 179 + 172 180 "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], 173 181 174 182 "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], ··· 177 185 178 186 "cacheable-request": ["cacheable-request@7.0.4", "", { "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", "http-cache-semantics": "^4.0.0", "keyv": "^4.0.0", "lowercase-keys": "^2.0.0", "normalize-url": "^6.0.1", "responselike": "^2.0.0" } }, "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg=="], 179 187 188 + "caniuse-lite": ["caniuse-lite@1.0.30001723", "", {}, "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw=="], 189 + 180 190 "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], 181 191 182 192 "chokidar": ["chokidar@3.5.3", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw=="], ··· 207 217 208 218 "defer-to-connect": ["defer-to-connect@2.0.1", "", {}, "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg=="], 209 219 220 + "electron-to-chromium": ["electron-to-chromium@1.5.170", "", {}, "sha512-GP+M7aeluQo9uAyiTCxgIj/j+PrWhMlY7LFVj8prlsPljd0Fdg9AprlfUi+OCSFWy9Y5/2D/Jrj9HS8Z4rpKWA=="], 221 + 210 222 "elm": ["elm@0.19.1-6", "", { "optionalDependencies": { "@elm_binaries/darwin_arm64": "0.19.1-0", "@elm_binaries/darwin_x64": "0.19.1-0", "@elm_binaries/linux_x64": "0.19.1-0", "@elm_binaries/win32_x64": "0.19.1-0" }, "bin": { "elm": "bin/elm" } }, "sha512-mKYyierHICPdMx/vhiIacdPmTPnh889gjHOZ75ZAoCxo3lZmSWbGP8HMw78wyctJH0HwvTmeKhlYSWboQNYPeQ=="], 211 223 212 224 "elm-land": ["elm-land@0.20.1", "", { "dependencies": { "@lydell/elm": "0.19.1-14", "chokidar": "3.5.3", "terser": "5.15.1", "typescript": "4.9.3", "vite": "5.2.8", "vite-plugin-elm-watch": "1.3.3" }, "bin": { "elm-land": "src/index.js" } }, "sha512-AY8BxYNT7mblaIO9SS2YQPdskZdMsLL6fqjAA5bORdkGIRDkMeaw+rXgiVSHUM2+TK0k/ld0TdQEAd24Moi5nw=="], ··· 223 235 224 236 "esbuild": ["esbuild@0.20.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.20.2", "@esbuild/android-arm": "0.20.2", "@esbuild/android-arm64": "0.20.2", "@esbuild/android-x64": "0.20.2", "@esbuild/darwin-arm64": "0.20.2", "@esbuild/darwin-x64": "0.20.2", "@esbuild/freebsd-arm64": "0.20.2", "@esbuild/freebsd-x64": "0.20.2", "@esbuild/linux-arm": "0.20.2", "@esbuild/linux-arm64": "0.20.2", "@esbuild/linux-ia32": "0.20.2", "@esbuild/linux-loong64": "0.20.2", "@esbuild/linux-mips64el": "0.20.2", "@esbuild/linux-ppc64": "0.20.2", "@esbuild/linux-riscv64": "0.20.2", "@esbuild/linux-s390x": "0.20.2", "@esbuild/linux-x64": "0.20.2", "@esbuild/netbsd-x64": "0.20.2", "@esbuild/openbsd-x64": "0.20.2", "@esbuild/sunos-x64": "0.20.2", "@esbuild/win32-arm64": "0.20.2", "@esbuild/win32-ia32": "0.20.2", "@esbuild/win32-x64": "0.20.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g=="], 225 237 238 + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], 239 + 226 240 "fastest-levenshtein": ["fastest-levenshtein@1.0.16", "", {}, "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg=="], 227 241 228 242 "fdir": ["fdir@6.4.6", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w=="], ··· 233 247 234 248 "folder-hash": ["folder-hash@3.3.3", "", { "dependencies": { "debug": "^4.1.1", "graceful-fs": "~4.2.0", "minimatch": "~3.0.4" }, "bin": { "folder-hash": "bin/folder-hash" } }, "sha512-SDgHBgV+RCjrYs8aUwCb9rTgbTVuSdzvFmLaChsLre1yf+D64khCW++VYciaByZ8Rm0uKF8R/XEpXuTRSGUM1A=="], 235 249 250 + "fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="], 251 + 236 252 "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], 253 + 254 + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], 237 255 238 256 "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], 239 257 ··· 245 263 246 264 "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], 247 265 266 + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], 267 + 248 268 "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], 249 269 250 270 "http2-wrapper": ["http2-wrapper@1.0.3", "", { "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" } }, "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg=="], ··· 254 274 "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], 255 275 256 276 "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="], 277 + 278 + "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], 257 279 258 280 "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], 259 281 ··· 291 313 292 314 "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], 293 315 316 + "missing.css": ["missing.css@1.1.3", "", {}, "sha512-dkGzliE9Zcv9tGPvuIHy1lpMc4Y5QAc+svF7Nqi+EMl4taRC5HfjARg55YC2jC9cbqOwX0qOUltUxqn57YIQiw=="], 317 + 294 318 "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], 295 319 296 320 "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], 297 321 322 + "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="], 323 + 298 324 "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="], 325 + 326 + "normalize-range": ["normalize-range@0.1.2", "", {}, "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA=="], 299 327 300 328 "normalize-url": ["normalize-url@6.1.0", "", {}, "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A=="], 301 329 ··· 315 343 316 344 "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], 317 345 346 + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], 347 + 318 348 "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], 319 349 320 350 "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="], 321 351 352 + "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], 353 + 322 354 "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], 323 355 356 + "postcss-import": ["postcss-import@16.1.1", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ=="], 357 + 358 + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], 359 + 324 360 "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], 325 361 326 362 "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], 327 363 328 364 "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], 329 365 366 + "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="], 367 + 330 368 "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], 331 369 332 370 "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], 371 + 372 + "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="], 333 373 334 374 "resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="], 335 375 ··· 367 407 368 408 "supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="], 369 409 410 + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], 411 + 370 412 "terminal-link": ["terminal-link@2.1.1", "", { "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" } }, "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ=="], 371 413 372 414 "terser": ["terser@5.15.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-K1faMUvpm/FBxjBXud0LWVAGxmvoPbZbfTCYbSgaaYQaIXI3/TdI7a7ZGA73Zrou6Q8Zmz3oeUTsp/dj+ag2Xw=="], ··· 382 424 "typescript": ["typescript@4.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA=="], 383 425 384 426 "undici-types": ["undici-types@7.8.0", "", {}, "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw=="], 427 + 428 + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], 385 429 386 430 "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], 387 431
M
web/package.json
··· 4 4 "postinstall": "elm-tooling install" 5 5 }, 6 6 "devDependencies": { 7 + "autoprefixer": "^10.4.21", 7 8 "elm-land": "^0.20.1", 8 9 "elm-review": "^2.13.2", 9 - "elm-tooling": "^1.15.1" 10 + "elm-tooling": "^1.15.1", 11 + "missing.css": "^1.1.3", 12 + "postcss": "^8.5.6", 13 + "postcss-import": "^16.1.1" 10 14 } 11 15 }
M
web/src/Auth.elm
··· 1 1 module Auth exposing (User, onPageLoad, viewCustomPage) 2 2 3 3 import Auth.Action 4 +import Auth.User 4 5 import Dict 5 6 import Route exposing (Route) 6 7 import Route.Path ··· 9 10 10 11 11 12 type alias User = 12 - { accessToken : String 13 - , refreshToken : String 14 - } 13 + Auth.User.User 15 14 16 15 17 16 {-| Called before an auth-only page is loaded. 18 17 -} 19 18 onPageLoad : Shared.Model -> Route () -> Auth.Action.Action User 20 19 onPageLoad shared _ = 21 - case shared.credentials of 22 - Just credentials -> 23 - Auth.Action.loadPageWithUser 24 - { accessToken = credentials.accessToken 25 - , refreshToken = credentials.refreshToken 26 - } 27 - 28 - _ -> 20 + case shared.user of 21 + Auth.User.NotSignedIn -> 29 22 Auth.Action.pushRoute 30 23 { path = Route.Path.Auth 31 24 , query = Dict.empty 32 25 , hash = Nothing 33 26 } 27 + 28 + Auth.User.SignedIn credentials -> 29 + Auth.Action.loadPageWithUser credentials 34 30 35 31 36 32 {-| Renders whenever `Auth.Action.loadCustomPage` is returned from `onPageLoad`.
M
web/src/Effect.elm
··· 5 5 , pushRoute, replaceRoute 6 6 , pushRoutePath, replaceRoutePath 7 7 , loadExternalUrl, back 8 - , sendApiRequest 8 + , sendApiRequest, refreshTokens 9 9 , signin, logout, saveUser, clearUser 10 - , map, toCmd, refreshTokens 10 + , map, toCmd 11 11 ) 12 12 13 13 {-| ··· 29 29 -} 30 30 31 31 import Api exposing (HttpRequestDetails) 32 +import Auth.User 32 33 import Browser.Navigation 33 34 import Data.Credentials exposing (Credentials) 34 35 import Dict exposing (Dict) ··· 308 309 let 309 310 headers : List Http.Header 310 311 headers = 311 - case options.shared.credentials of 312 - Just tok -> 312 + case options.shared.user of 313 + Auth.User.SignedIn cred -> 313 314 if not (String.contains opts.endpoint "refresh-tokens") then 314 - [ Http.header "Authorization" ("Bearer " ++ tok.accessToken) ] 315 + [ Http.header "Authorization" ("Bearer " ++ cred.accessToken) ] 315 316 316 317 else 317 318 [] 318 319 319 - Nothing -> 320 + Auth.User.NotSignedIn -> 320 321 [] 321 322 in 322 323 Http.request
A
web/src/Layouts/Header.elm
··· 1 +module Layouts.Header exposing (Model, Msg, Props, layout) 2 + 3 +import Auth.User 4 +import Effect exposing (Effect) 5 +import Html exposing (Html) 6 +import Html.Attributes as Attr 7 +import Html.Events 8 +import Layout exposing (Layout) 9 +import Route exposing (Route) 10 +import Route.Path 11 +import Shared 12 +import View exposing (View) 13 + 14 + 15 +type alias Props = 16 + {} 17 + 18 + 19 +layout : Props -> Shared.Model -> Route () -> Layout () Model Msg contentMsg 20 +layout _ shared _ = 21 + Layout.new 22 + { init = init 23 + , update = update 24 + , view = view shared 25 + , subscriptions = subscriptions 26 + } 27 + 28 + 29 + 30 +-- MODEL 31 + 32 + 33 +type alias Model = 34 + {} 35 + 36 + 37 +init : () -> ( Model, Effect Msg ) 38 +init _ = 39 + ( {}, Effect.none ) 40 + 41 + 42 + 43 +-- UPDATE 44 + 45 + 46 +type Msg 47 + = UserClickedLogout 48 + 49 + 50 +update : Msg -> Model -> ( Model, Effect Msg ) 51 +update msg model = 52 + case msg of 53 + UserClickedLogout -> 54 + ( model, Effect.logout ) 55 + 56 + 57 +subscriptions : Model -> Sub Msg 58 +subscriptions _ = 59 + Sub.none 60 + 61 + 62 + 63 +-- VIEW 64 + 65 + 66 +view : Shared.Model -> { toContentMsg : Msg -> contentMsg, content : View contentMsg, model : Model } -> View contentMsg 67 +view shared { toContentMsg, content } = 68 + { title = content.title 69 + , body = 70 + [ viewNavbar shared |> Html.map toContentMsg 71 + , Html.main_ [] content.body 72 + ] 73 + } 74 + 75 + 76 +viewNavbar : Shared.Model -> Html Msg 77 +viewNavbar shared = 78 + Html.header [ Attr.class "navbar" ] 79 + [ Html.nav [ Attr.class "f-row justify-content:space-between" ] 80 + [ Html.ul [ Attr.attribute "role" "list" ] 81 + [ Html.li [] [ viewNavLink ( "home", Route.Path.Home_ ) ] ] 82 + , Html.ul [ Attr.attribute "role" "list" ] 83 + (case shared.user of 84 + Auth.User.SignedIn _ -> 85 + [ Html.li [] [ viewNavLink ( "profile", Route.Path.Profile_Me ) ] 86 + , Html.li [] [ Html.a [ Html.Events.onClick UserClickedLogout ] [ Html.text "logout" ] ] 87 + ] 88 + 89 + Auth.User.NotSignedIn -> 90 + [ Html.li [] [ viewNavLink ( "sign in", Route.Path.Auth ) ] 91 + ] 92 + ) 93 + ] 94 + ] 95 + 96 + 97 +viewNavLink : ( String, Route.Path.Path ) -> Html msg 98 +viewNavLink ( label, path ) = 99 + Html.a 100 + [ Route.Path.href path ] 101 + [ Html.text label ]
M
web/src/Pages/Auth.elm
··· 2 2 3 3 import Api 4 4 import Api.Auth 5 +import Auth.User 5 6 import Data.Credentials exposing (Credentials) 6 7 import Effect exposing (Effect) 7 8 import Html exposing (Html) 8 9 import Html.Attributes as Attr 9 10 import Html.Events 10 11 import Http 12 +import Layouts 11 13 import Page exposing (Page) 12 14 import Route exposing (Route) 13 15 import Route.Path ··· 23 25 , subscriptions = subscriptions 24 26 , view = view 25 27 } 28 + |> Page.withLayout (\_ -> Layouts.Header {}) 26 29 27 30 28 31 ··· 48 51 , formVariant = SignIn 49 52 , error = Nothing 50 53 } 51 - , case shared.credentials of 52 - Just _ -> 54 + , case shared.user of 55 + Auth.User.SignedIn _ -> 53 56 Effect.pushRoutePath Route.Path.Home_ 54 57 55 - Nothing -> 58 + Auth.User.NotSignedIn -> 56 59 Effect.none 57 60 ) 58 61 ··· 146 149 view model = 147 150 { title = "Authentication" 148 151 , body = 149 - [ Html.div [] 152 + [ Html.div [ Attr.class "center" ] 150 153 -- TODO: add oauth buttons 151 - [ viewChangeVariant model.formVariant 152 - , viewError model.error 154 + [ viewError model.error 155 + , viewChangeVariant model.formVariant 153 156 , viewForm model 157 + , viewForgotPassword 154 158 ] 155 159 ] 156 160 } ··· 158 162 159 163 viewChangeVariant : Variant -> Html Msg 160 164 viewChangeVariant variant = 161 - Html.div [] 165 + Html.div [ Attr.class "mb1" ] 162 166 [ Html.button 163 167 [ Attr.disabled (variant == SignIn) 164 168 , Html.Events.onClick (UserChangedFormVariant SignIn) ··· 179 183 SignIn -> 180 184 [ viewFormInput { field = Email, value = model.email } 181 185 , viewFormInput { field = Password, value = model.password } 182 - , viewFormControls model 186 + , viewSubmitButton model 183 187 ] 184 188 185 189 SignUp -> 186 190 [ viewFormInput { field = Email, value = model.email } 187 191 , viewFormInput { field = Password, value = model.password } 188 192 , viewFormInput { field = PasswordAgain, value = model.passwordAgain } 189 - , viewFormControls model 193 + , viewSubmitButton model 190 194 ] 191 195 ) 192 196 ··· 195 199 viewError maybeError = 196 200 case maybeError of 197 201 Just error -> 198 - Html.div [ Attr.style "color" "red" ] 199 - [ Html.text (Api.errorToFriendlyMessage error) ] 202 + Html.div [ Attr.class "box bad" ] 203 + [ Html.strong [ Attr.class "block titlebar" ] [ Html.text "Error" ] 204 + , Html.text (Api.errorToFriendlyMessage error) 205 + ] 200 206 201 207 Nothing -> 202 208 Html.text "" ··· 204 210 205 211 viewFormInput : { field : Field, value : String } -> Html Msg 206 212 viewFormInput opts = 207 - Html.div [] 213 + Html.div [ Attr.class "mb1" ] 208 214 [ Html.label [] [ Html.text (fromFieldToLabel opts.field) ] 209 215 , Html.div [] 210 216 [ Html.input ··· 217 223 ] 218 224 219 225 220 -viewFormControls : Model -> Html Msg 221 -viewFormControls model = 226 +viewForgotPassword : Html Msg 227 +viewForgotPassword = 222 228 Html.div [] 229 + [ Html.a 230 + [ Attr.href "/forgot-password" 231 + , Attr.class "gray" 232 + ] 233 + [ Html.text "Forgot password?" ] 234 + ] 235 + 236 + 237 +viewSubmitButton : Model -> Html Msg 238 +viewSubmitButton model = 239 + Html.div [ Attr.class "mb1" ] 223 240 [ Html.button 224 241 [ Attr.disabled (isFormDisabled model) ] 225 - (case model.formVariant of 226 - SignIn -> 227 - [ Html.text "Sign In" ] 228 - 229 - SignUp -> 230 - [ Html.text "Sign Up" ] 231 - ) 242 + [ Html.text (fromVariantToLabel model.formVariant) ] 232 243 ] 233 244 234 245 ··· 246 257 || String.isEmpty model.password 247 258 || String.isEmpty model.passwordAgain 248 259 || (model.password /= model.passwordAgain) 260 + 261 + 262 +fromVariantToLabel : Variant -> String 263 +fromVariantToLabel variant = 264 + case variant of 265 + SignIn -> 266 + "Sign In" 267 + 268 + SignUp -> 269 + "Sign Up" 249 270 250 271 251 272 fromFieldToLabel : Field -> String
M
web/src/Pages/Home_.elm
··· 2 2 3 3 import Effect exposing (Effect) 4 4 import Html 5 -import Html.Attributes as Attributes 6 5 import Html.Events 6 +import Layouts 7 7 import Page exposing (Page) 8 8 import Route exposing (Route) 9 9 import Shared ··· 18 18 , subscriptions = subscriptions 19 19 , view = view shared 20 20 } 21 + |> Page.withLayout Layouts.Header 21 22 22 23 23 24 ··· 38 39 39 40 40 41 type Msg 41 - = LogOut 42 + = NoOp 42 43 43 44 44 45 update : Msg -> Model -> ( Model, Effect Msg ) 45 46 update msg model = 46 47 case msg of 47 - LogOut -> 48 - ( model, Effect.logout ) 48 + NoOp -> 49 + ( model, Effect.none ) 49 50 50 51 51 52 ··· 64 65 view : Shared.Model -> Model -> View Msg 65 66 view _ _ = 66 67 { title = "Homepage" 67 - , body = 68 - [ Html.div [] 69 - [ Html.p [] [ Html.text "Hello, world!" ] 70 - , Html.p [] 71 - [ Html.a 72 - [ Attributes.href "/profile/me" ] 73 - [ Html.text "/profile/me - fetches authorized data" ] 74 - ] 75 - , Html.p [] 76 - [ Html.button 77 - [ Html.Events.onClick LogOut ] 78 - [ Html.text "Logout" ] 79 - ] 80 - ] 81 - ] 68 + , body = [ Html.p [ Html.Events.onClick NoOp ] [ Html.text "Hello, world!" ] ] 82 69 }
M
web/src/Pages/Profile/Me.elm
··· 7 7 import Effect exposing (Effect) 8 8 import Html exposing (Html) 9 9 import Http 10 +import Layouts 10 11 import Page exposing (Page) 11 12 import Route exposing (Route) 12 13 import Shared ··· 21 22 , subscriptions = subscriptions 22 23 , view = view shared 23 24 } 25 + |> Page.withLayout (\_ -> Layouts.Header {}) 24 26 25 27 26 28
M
web/src/interop.js
··· 1 +import "./styles.css"; 2 + 1 3 export const flags = (_) => { 2 4 return { 3 5 access_token: JSON.parse(window.localStorage.access_token || 'null'), ··· 8 10 export const onReady = ({ app }) => { 9 11 if (app.ports?.sendToLocalStorage) { 10 12 app.ports.sendToLocalStorage.subscribe(({ key, value }) => { 11 - window.localStorage[key] = JSON.stringify(value) 13 + window.localStorage[key] = JSON.stringify(value); 12 14 }) 13 15 } 14 16 }