diff --git a/.vscode/extensions.json b/.vscode/extensions.json index df38b8d..9a49c32 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,7 +1,6 @@ { "recommendations": [ "jakebecker.elixir-ls", - "ms-vscode.vscode-typescript-tslint-plugin", "kevinmcgowan.typescriptimport", "msjsdiag.debugger-for-chrome" ] diff --git a/backend/mix.lock b/backend/mix.lock index 64224d8..51d8269 100644 --- a/backend/mix.lock +++ b/backend/mix.lock @@ -1,66 +1,66 @@ %{ - "appsignal": {:hex, :appsignal, "1.10.13", "d5df34ac7dc2d937510716f2089cc5f1d45b3f10f38225d19c35f31810b9266d", [:make, :mix], [{:decorator, "~> 1.2.3", [hex: :decorator, repo: "hexpm", optional: false]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, ">= 1.3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "appsignal": {:hex, :appsignal, "1.10.1", "582238fd95cef54d1440ce9b2ef47c243e7d176200156164b2d924c29c7cef24", [:make, :mix], [{:decorator, "~> 1.2.3", [hex: :decorator, repo: "hexpm", optional: false]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.2.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:plug, ">= 1.1.0", [hex: :plug, repo: "hexpm", optional: true]}, {:poison, ">= 1.3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "686bf26b678d958a366215923de605492f99ca6ad7715a89a4abaf9caf6ea45d"}, "artificery": {:hex, :artificery, "0.4.2", "3ded6e29e13113af52811c72f414d1e88f711410cac1b619ab3a2666bbd7efd4", [:mix], [], "hexpm"}, "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, - "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, - "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"}, - "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"}, - "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"}, - "corsica": {:hex, :corsica, "1.1.3", "5f1de40bc9285753aa03afbdd10c364dac79b2ddbf2ba9c5c9c47b397ec06f40", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, - "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"}, - "credo": {:hex, :credo, "1.3.2", "08d456dcf3c24da162d02953fb07267e444469d8dad3a2ae47794938ea467b3a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, - "crontab": {:hex, :crontab, "1.1.10", "dc9bb1f4299138d47bce38341f5dcbee0aa6c205e864fba7bc847f3b5cb48241", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, - "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm"}, - "decorator": {:hex, :decorator, "1.2.4", "31dfff6143d37f0b68d0bffb3b9f18ace14fea54d4f1b5e4f86ead6f00d9ff6e", [:mix], [], "hexpm"}, + "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, + "certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "805abd97539caf89ec6d4732c91e62ba9da0cda51ac462380bbd28ee697a8c42"}, + "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, + "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"}, + "corsica": {:hex, :corsica, "1.1.3", "5f1de40bc9285753aa03afbdd10c364dac79b2ddbf2ba9c5c9c47b397ec06f40", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "8156b3a14a114a346262871333a931a1766b2597b56bf994fcfcb65443a348ad"}, + "cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "04fd8c6a39edc6aaa9c26123009200fc61f92a3a94f3178c527b70b767c6e605"}, + "cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm", "79f954a7021b302186a950a32869dbc185523d99d3e44ce430cd1f3289f41ed4"}, + "credo": {:hex, :credo, "1.4.0", "92339d4cbadd1e88b5ee43d427b639b68a11071b6f73854e33638e30a0ea11f5", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "1fd3b70dce216574ce3c18bdf510b57e7c4c85c2ec9cad4bff854abaf7e58658"}, + "crontab": {:hex, :crontab, "1.1.10", "dc9bb1f4299138d47bce38341f5dcbee0aa6c205e864fba7bc847f3b5cb48241", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "1347d889d1a0eda997990876b4894359e34bfbbd688acbb0ba28a2795ca40685"}, + "db_connection": {:hex, :db_connection, "2.2.2", "3bbca41b199e1598245b716248964926303b5d4609ff065125ce98bcd368939e", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "642af240d8a8affb93b4ba5a6fcd2bbcbdc327e1a524b825d383711536f8070c"}, + "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, + "decorator": {:hex, :decorator, "1.2.4", "31dfff6143d37f0b68d0bffb3b9f18ace14fea54d4f1b5e4f86ead6f00d9ff6e", [:mix], [], "hexpm", "6c393a3aada02d0eaa6bde725e1816d2b122d7d0fb06c6dd8ebd92d33826396b"}, "distillery": {:hex, :distillery, "2.1.1", "f9332afc2eec8a1a2b86f22429e068ef35f84a93ea1718265e740d90dd367814", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"}, - "ecto": {:hex, :ecto, "3.4.2", "6890af71025769bd27ef62b1ed1925cfe23f7f0460bcb3041da4b705215ff23e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, - "ecto_sql": {:hex, :ecto_sql, "3.4.2", "3d842665a81ba2137b62aa70151afe81dae44824cd09b2076a255937ab4e2dc9", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, - "elasticsearch": {:hex, :elasticsearch, "1.0.0", "626d3fb8e7554d9c93eb18817ae2a3d22c2a4191cc903c4644b1334469b15374", [:mix], [{:httpoison, ">= 0.0.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sigaws, "~> 0.7", [hex: :sigaws, repo: "hexpm", optional: true]}, {:vex, "~> 0.6.0", [hex: :vex, repo: "hexpm", optional: false]}], "hexpm"}, - "ex_twilio": {:hex, :ex_twilio, "0.7.0", "d7ce624ef4661311ae28c3e3aa060ecb66a9f4843184d7400c29072f7d3f5a4a", [:mix], [{:httpoison, ">= 0.9.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:inflex, "~> 1.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.0", [hex: :joken, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "gen_stage": {:hex, :gen_stage, "1.0.0", "51c8ae56ff54f9a2a604ca583798c210ad245f415115453b773b621c49776df5", [:mix], [], "hexpm"}, - "gen_state_machine": {:hex, :gen_state_machine, "2.1.0", "a38b0e53fad812d29ec149f0d354da5d1bc0d7222c3711f3a0bd5aa608b42992", [:mix], [], "hexpm"}, - "gettext": {:hex, :gettext, "0.17.4", "f13088e1ec10ce01665cf25f5ff779e7df3f2dc71b37084976cf89d1aa124d5c", [:mix], [], "hexpm"}, - "gollum": {:hex, :gollum, "0.3.3", "25ebb47700b9236bc4e5382bf91b72e4cdaf9bae3556172eff27e770735a198f", [:mix], [{:httpoison, "~> 1.5.1", [hex: :httpoison, repo: "hexpm", optional: false]}], "hexpm"}, - "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, - "honeydew": {:hex, :honeydew, "1.4.5", "03818730602274ef0119652d664b92ddf733256e857d29899ce6841e01345bd1", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, - "httpoison": {:hex, :httpoison, "1.5.1", "0f55b5b673b03c5c327dac7015a67cb571b99b631acc0bc1b0b98dcd6b9f2104", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "hunter": {:hex, :hunter, "0.5.1", "374dc4a800e2c340659657f8875e466075c7ea532e0d7a7787665f272b410150", [:mix], [{:httpoison, "~> 1.5", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 4.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, - "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, - "inflex": {:hex, :inflex, "1.10.0", "8366a7696e70e1813aca102e61274addf85d99f4a072b2f9c7984054ea1b9d29", [:mix], [], "hexpm"}, - "jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, - "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm"}, - "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm"}, - "libring": {:hex, :libring, "1.5.0", "44313eb6862f5c9168594a061e9d5f556a9819da7c6444706a9e2da533396d70", [:mix], [], "hexpm"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, - "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, - "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm"}, - "nebulex": {:hex, :nebulex, "1.1.1", "4117e18e614ecbd078e19558b7b9c58f11d666c4dca584b9382b02913f13ad8a", [:mix], [{:shards, "~> 0.6", [hex: :shards, repo: "hexpm", optional: false]}], "hexpm"}, + "ecto": {:hex, :ecto, "3.4.4", "a2c881e80dc756d648197ae0d936216c0308370332c5e77a2325a10293eef845", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4bd3ad62abc3b21fb629f0f7a3dab23a192fca837d257dd08449fba7373561"}, + "ecto_sql": {:hex, :ecto_sql, "3.4.3", "c552aa8a7ccff2b64024f835503b3155d8e73452c180298527fbdbcd6e79710b", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0 or ~> 0.4.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.0", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ec9e59d6fa3f8cfda9963ada371e9e6659167c2338a997bd7ea23b10b245842b"}, + "elasticsearch": {:hex, :elasticsearch, "1.0.0", "626d3fb8e7554d9c93eb18817ae2a3d22c2a4191cc903c4644b1334469b15374", [:mix], [{:httpoison, ">= 0.0.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sigaws, "~> 0.7", [hex: :sigaws, repo: "hexpm", optional: true]}, {:vex, "~> 0.6.0", [hex: :vex, repo: "hexpm", optional: false]}], "hexpm", "9fa0b717ad57a54c28451b3eb10c5121211c29a7b33615d2bcc7e2f3c9418b2e"}, + "ex_twilio": {:hex, :ex_twilio, "0.7.0", "d7ce624ef4661311ae28c3e3aa060ecb66a9f4843184d7400c29072f7d3f5a4a", [:mix], [{:httpoison, ">= 0.9.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:inflex, "~> 1.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:joken, "~> 2.0", [hex: :joken, repo: "hexpm", optional: false]}, {:poison, "~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "6be84f1508ed47d443d18cdc4ea0561f8ad4095b69791dd9be5f2fe14b1dafc5"}, + "gen_stage": {:hex, :gen_stage, "1.0.0", "51c8ae56ff54f9a2a604ca583798c210ad245f415115453b773b621c49776df5", [:mix], [], "hexpm", "1d9fc978db5305ac54e6f5fec7adf80cd893b1000cf78271564c516aa2af7706"}, + "gen_state_machine": {:hex, :gen_state_machine, "2.1.0", "a38b0e53fad812d29ec149f0d354da5d1bc0d7222c3711f3a0bd5aa608b42992", [:mix], [], "hexpm", "ae367038808db25cee2f2c4b8d0531522ea587c4995eb6f96ee73410a60fa06b"}, + "gettext": {:hex, :gettext, "0.18.0", "406d6b9e0e3278162c2ae1de0a60270452c553536772167e2d701f028116f870", [:mix], [], "hexpm", "c3f850be6367ebe1a08616c2158affe4a23231c70391050bf359d5f92f66a571"}, + "gollum": {:hex, :gollum, "0.3.3", "25ebb47700b9236bc4e5382bf91b72e4cdaf9bae3556172eff27e770735a198f", [:mix], [{:httpoison, "~> 1.5.1", [hex: :httpoison, repo: "hexpm", optional: false]}], "hexpm", "39268eeaf4f0adb6fdebe4f8c36b10a277881ab2eee3419c9b6727759e2f5a5d"}, + "hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"}, + "honeydew": {:hex, :honeydew, "1.4.5", "03818730602274ef0119652d664b92ddf733256e857d29899ce6841e01345bd1", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "62633858ce7e82f67689b4d2b4024bd87fa00dc6a11a227614b816d868a1529d"}, + "httpoison": {:hex, :httpoison, "1.5.1", "0f55b5b673b03c5c327dac7015a67cb571b99b631acc0bc1b0b98dcd6b9f2104", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "191a3b6329c917de4e7ca68431919a59bf19e60694b313a69bc1f56a4cb160bf"}, + "hunter": {:hex, :hunter, "0.5.1", "374dc4a800e2c340659657f8875e466075c7ea532e0d7a7787665f272b410150", [:mix], [{:httpoison, "~> 1.5", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 4.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "209b2cca7e4d51d5ff7ee4a0ab6cdc4c6ad23ddd61c9e12ceeee6f7ffbeae9c8"}, + "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "8fddb3aec4692c71647d67de72536254bce9069851754e370a99f2aae69fbdf4"}, + "inflex": {:hex, :inflex, "1.10.0", "8366a7696e70e1813aca102e61274addf85d99f4a072b2f9c7984054ea1b9d29", [:mix], [], "hexpm", "7b5ccb9b720c26516f5962dc4565fc26f083ca107b0f6c167048506a125d2df3"}, + "jason": {:hex, :jason, "1.2.1", "12b22825e22f468c02eb3e4b9985f3d0cb8dc40b9bd704730efa11abd2708c44", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b659b8571deedf60f79c5a608e15414085fa141344e2716fbd6988a084b5f993"}, + "joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"}, + "jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"}, + "libring": {:hex, :libring, "1.5.0", "44313eb6862f5c9168594a061e9d5f556a9819da7c6444706a9e2da533396d70", [:mix], [], "hexpm", "04e843d4fdcff49a62d8e03778d17c6cb2a03fe2d14020d3825a1761b55bd6cc"}, + "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"}, + "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, + "nebulex": {:hex, :nebulex, "1.1.1", "4117e18e614ecbd078e19558b7b9c58f11d666c4dca584b9382b02913f13ad8a", [:mix], [{:shards, "~> 0.6", [hex: :shards, repo: "hexpm", optional: false]}], "hexpm", "cf3a04f9bfb8fcb8f070ab049c3fab54dd31c72d13430360a1c908c3cacb9196"}, "paginator": {:hex, :paginator, "0.6.0", "bc2c01abdd98281ff39b6a7439cf540091122a7927bdaabc167c61d4508f9cbb", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"}, - "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, - "phoenix": {:hex, :phoenix, "1.4.16", "2cbbe0c81e6601567c44cc380c33aa42a1372ac1426e3de3d93ac448a7ec4308", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"}, - "plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.1.3", "38999a3e85e39f0e6bdfdf820761abac61edde1632cfebbacc445cdcb6ae1333", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, - "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm"}, - "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm"}, - "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, - "public_suffix": {:hex, :public_suffix, "0.6.0", "100cfe86f13f9f6f0cf67e743b1b83c78dd1223a2c422fa03ebf4adff514cbc3", [:mix], [{:idna, ">= 1.2.0 and < 6.0.0", [hex: :idna, repo: "hexpm", optional: false]}], "hexpm"}, - "quantum": {:hex, :quantum, "2.4.0", "f2ad4b20988f848455d35ed0e884ba0c7629a27ee86cbec6a6e0fc214b6e69cf", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12 or ~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: true]}], "hexpm"}, - "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, - "recase": {:hex, :recase, "0.6.0", "1dd2dd2f4e06603b74977630e739f08b7fedbb9420cc14de353666c2fc8b99f4", [:mix], [], "hexpm"}, - "scrivener": {:hex, :scrivener, "2.7.0", "fa94cdea21fad0649921d8066b1833d18d296217bfdf4a5389a2f45ee857b773", [:mix], [], "hexpm"}, - "scrivener_ecto": {:hex, :scrivener_ecto, "2.3.0", "057f9dd3c77315f0a470263c3565353860d0294404aed611b3524c6df9044189", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm"}, - "shards": {:hex, :shards, "0.6.2", "e05d05537883220c3b8a8f9d40d5c8ba7ff6064c63ebb6b23046972f6863b2d1", [:make, :rebar3], [], "hexpm"}, - "sobelow": {:hex, :sobelow, "0.10.1", "7ddd72eacd3cff0d8ebaaa7825a11718652d264a8c1b97fe802d09634c955db5", [:mix], [], "hexpm"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"}, - "swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm"}, - "swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"}, - "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm"}, - "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, - "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"}, - "vex": {:hex, :vex, "0.6.0", "4e79b396b2ec18cd909eed0450b19108d9631842598d46552dc05031100b7a56", [:mix], [], "hexpm"}, + "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, + "phoenix": {:hex, :phoenix, "1.4.17", "1b1bd4cff7cfc87c94deaa7d60dd8c22e04368ab95499483c50640ef3bd838d8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a8e5d7a3d76d452bb5fb86e8b7bd115f737e4f8efe202a463d4aeb4a5809611"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.1.0", "a044d0756d0464c5a541b4a0bf4bcaf89bffcaf92468862408290682c73ae50d", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "c5e666a341ff104d0399d8f0e4ff094559b2fde13a5985d4cb5023b2c2ac558b"}, + "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"}, + "plug": {:hex, :plug, "1.10.1", "c56a6d9da7042d581159bcbaef873ba9d87f15dce85420b0d287bca19f40f9bd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "b5cd52259817eb8a31f2454912ba1cff4990bca7811918878091cb2ab9e52cb8"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.2.1", "fcf58aa33227a4322a050e4783ee99c63c031a2e7f9a2eb7340d55505e17f30f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3b43de24460d87c0971887286e7a20d40462e48eb7235954681a20cee25ddeb6"}, + "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"}, + "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, + "postgrex": {:hex, :postgrex, "0.15.4", "5d691c25fc79070705a2ff0e35ce0822b86a0ee3c6fdb7a4fb354623955e1aed", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "306515b9d975fcb2478dc337a1d27dc3bf8af7cd71017c333fe9db3a3d211b0a"}, + "public_suffix": {:hex, :public_suffix, "0.6.0", "100cfe86f13f9f6f0cf67e743b1b83c78dd1223a2c422fa03ebf4adff514cbc3", [:mix], [{:idna, ">= 1.2.0 and < 6.0.0", [hex: :idna, repo: "hexpm", optional: false]}], "hexpm", "663f29209e7930680cb1656cf144cc7484b37fe261f4417fec280d9a20363bfc"}, + "quantum": {:hex, :quantum, "2.4.0", "f2ad4b20988f848455d35ed0e884ba0c7629a27ee86cbec6a6e0fc214b6e69cf", [:mix], [{:calendar, "~> 0.17", [hex: :calendar, repo: "hexpm", optional: true]}, {:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}, {:gen_stage, "~> 0.12 or ~> 1.0", [hex: :gen_stage, repo: "hexpm", optional: false]}, {:swarm, "~> 3.3", [hex: :swarm, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: true]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: true]}], "hexpm", "a125a9e65a5af740a1198f3b05c1a736fce3942f5e0dc2901e0f9be5745bea99"}, + "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, + "recase": {:hex, :recase, "0.6.0", "1dd2dd2f4e06603b74977630e739f08b7fedbb9420cc14de353666c2fc8b99f4", [:mix], [], "hexpm", "8712e318420a228eb2e6366ada230148ed3a4316a798319edd5512f64d78c990"}, + "scrivener": {:hex, :scrivener, "2.7.0", "fa94cdea21fad0649921d8066b1833d18d296217bfdf4a5389a2f45ee857b773", [:mix], [], "hexpm", "30da36a427f2519cf75993271fb7c5aad1759682a70f90d880a85c3d743d2c57"}, + "scrivener_ecto": {:hex, :scrivener_ecto, "2.3.0", "057f9dd3c77315f0a470263c3565353860d0294404aed611b3524c6df9044189", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:scrivener, "~> 2.4", [hex: :scrivener, repo: "hexpm", optional: false]}], "hexpm", "dfa43ca660651da63239e5d4acbfd9c57c5759bbf3a2bdc16cd70777c9bc7e0d"}, + "shards": {:hex, :shards, "0.6.2", "e05d05537883220c3b8a8f9d40d5c8ba7ff6064c63ebb6b23046972f6863b2d1", [:make, :rebar3], [], "hexpm", "58afa3712f1f1256a2a15e39fa95b7cd758087aaa7a25beaf786daabd87890f0"}, + "sobelow": {:hex, :sobelow, "0.10.2", "00e91208046d3b434f9f08779fe0ca7c6d6595b7fa33b289e792dffa6dde8081", [:mix], [], "hexpm", "e30fc994330cf6f485c1c4f2fb7c4b2d403557d0e101c6e5329fd17a58e55a7e"}, + "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, + "swarm": {:hex, :swarm, "3.4.0", "64f8b30055d74640d2186c66354b33b999438692a91be275bb89cdc7e401f448", [:mix], [{:gen_state_machine, "~> 2.0", [hex: :gen_state_machine, repo: "hexpm", optional: false]}, {:libring, "~> 1.0", [hex: :libring, repo: "hexpm", optional: false]}], "hexpm", "94884f84783fc1ba027aba8fe8a7dae4aad78c98e9f9c76667ec3471585c08c6"}, + "swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"}, + "telemetry": {:hex, :telemetry, "0.4.1", "ae2718484892448a24470e6aa341bc847c3277bfb8d4e9289f7474d752c09c7f", [:rebar3], [], "hexpm", "4738382e36a0a9a2b6e25d67c960e40e1a2c95560b9f936d8e29de8cd858480f"}, + "timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"}, + "tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"}, + "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm", "da1d9bef8a092cc7e1e51f1298037a5ddfb0f657fe862dfe7ba4c5807b551c29"}, + "vex": {:hex, :vex, "0.6.0", "4e79b396b2ec18cd909eed0450b19108d9631842598d46552dc05031100b7a56", [:mix], [], "hexpm", "7e4d9b50dd72cf931b52aba3470513686007f2ad54832de37cdb659cc85ba73e"}, } diff --git a/frontend/.eslintignore b/frontend/.eslintignore new file mode 100644 index 0000000..385644c --- /dev/null +++ b/frontend/.eslintignore @@ -0,0 +1,4 @@ +node_modules +dist +build +coverage \ No newline at end of file diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js new file mode 100644 index 0000000..56ee49e --- /dev/null +++ b/frontend/.eslintrc.js @@ -0,0 +1,24 @@ +module.exports = { + root: true, + parser: "@typescript-eslint/parser", + parserOptions: { + tsconfigRootDir: __dirname, + project: ["./tsconfig.json"], + }, + plugins: ["@typescript-eslint", "prettier"], + extends: [ + "plugin:@typescript-eslint/recommended-requiring-type-checking", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:react/recommended", + "plugin:react-hooks/recommended", + "prettier/@typescript-eslint", + "prettier", + ], + rules: { + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "react/prop-types": 0, + "@typescript-eslint/no-non-null-assertion": 0 + }, +}; diff --git a/frontend/.prettierrc.js b/frontend/.prettierrc.js new file mode 100644 index 0000000..a1204a7 --- /dev/null +++ b/frontend/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + printWidth: 100 +} \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index 9430137..9f973de 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -6,7 +6,7 @@ "start": "NODE_ENV=development react-scripts start", "build": "react-scripts build", "typecheck": "tsc --noemit", - "lint": "yarn typecheck && tslint -p tsconfig.json -c tslint.json \"src/**/*.{ts,tsx}\"", + "lint": "yarn typecheck && yarn eslint src/ --ext .js,.jsx,.ts,.tsx", "lint:fix": "yarn lint --fix", "pretty": "prettier --write \"src/**/*.{ts,tsx}\"", "test": "yarn lint && react-scripts test --ci", @@ -20,62 +20,67 @@ "lint-staged": { "src/**/*.{ts,tsx}": [ "yarn pretty", - "yarn lint:fix", - "git add" + "yarn lint:fix" ] }, "prettier": { "printWidth": 120 }, "dependencies": { - "@blueprintjs/core": "^3.19.1", - "@blueprintjs/icons": "^3.11.0", - "@blueprintjs/select": "^3.11.1", + "@blueprintjs/core": "^3.26.1", + "@blueprintjs/icons": "^3.16.0", + "@blueprintjs/select": "^3.12.3", "classnames": "^2.2.6", "connected-react-router": "^6.5.2", "cross-fetch": "^3.0.4", - "cytoscape": "^3.11.0", - "cytoscape-popper": "^1.0.4", + "cytoscape": "^3.15.0", + "cytoscape-popper": "^1.0.7", "inflection": "^1.12.0", "lodash": "^4.17.15", - "moment": "^2.22.2", + "moment": "^2.25.3", "normalize.css": "^8.0.0", "numeral": "^2.0.6", "react": "^16.10.2", "react-dom": "^16.10.2", "react-redux": "^7.1.1", - "react-router-dom": "^5.1.2", - "react-scripts": "^3.2.0", + "react-router-dom": "^5.2.0", + "react-scripts": "3.4.1", "react-sigma": "^1.2.30", "react-virtualized": "^9.21.1", "redux": "^4.0.4", "redux-thunk": "^2.3.0", "sanitize-html": "^1.20.1", - "styled-components": "^4.4.0", + "styled-components": "^5.1.0", "tippy.js": "^4.3.5" }, "devDependencies": { - "@blueprintjs/tslint-config": "^1.9.0", "@types/classnames": "^2.2.9", "@types/cytoscape": "^3.8.3", "@types/inflection": "^1.5.28", - "@types/jest": "^24.0.19", - "@types/lodash": "^4.14.144", - "@types/node": "^12.7.12", - "@types/numeral": "^0.0.26", - "@types/react": "^16.9.6", - "@types/react-dom": "^16.9.2", - "@types/react-redux": "^7.1.4", - "@types/react-router-dom": "^5.1.0", - "@types/sanitize-html": "^1.20.2", - "@types/styled-components": "4.1.19", - "husky": "^3.0.9", - "lint-staged": "^9.4.2", + "@types/jest": "^25.2.3", + "@types/lodash": "^4.14.151", + "@types/node": "^14.0.1", + "@types/numeral": "^0.0.28", + "@types/react": "^16.9.35", + "@types/react-dom": "^16.9.8", + "@types/react-redux": "^7.1.8", + "@types/react-router-dom": "^5.1.5", + "@types/sanitize-html": "^1.23.0", + "@types/styled-components": "5.1.0", + "@typescript-eslint/eslint-plugin": "^2.24.0", + "@typescript-eslint/parser": "^2.34.0", + "eslint-config-airbnb-typescript": "^7.2.1", + "eslint-config-prettier": "^6.11.0", + "eslint-plugin-import": "^2.20.1", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-prettier": "^3.1.3", + "eslint-plugin-react": "^7.20.0", + "eslint-plugin-react-hooks": "^4.0.2", + "husky": "^4.2.5", + "lint-staged": "^10.2.4", + "prettier": "^2.0.5", "react-axe": "^3.3.0", - "tslint": "^5.20.0", - "tslint-config-security": "^1.16.0", - "tslint-eslint-rules": "^5.4.0", - "typescript": "^3.6.4" + "typescript": "^3.9.2" }, "browserslist": [ ">0.2%", diff --git a/frontend/src/AppRouter.tsx b/frontend/src/AppRouter.tsx index 4ddda2b..149e11b 100644 --- a/frontend/src/AppRouter.tsx +++ b/frontend/src/AppRouter.tsx @@ -4,26 +4,26 @@ import { Classes } from "@blueprintjs/core"; import { ConnectedRouter } from "connected-react-router"; import { Route } from "react-router-dom"; -import { Nav } from "./components/organisms/"; +import { Nav } from "./components/organisms"; import { AboutScreen, AdminScreen, GraphScreen, LoginScreen, TableScreen, - VerifyLoginScreen -} from "./components/screens/"; + VerifyLoginScreen, +} from "./components/screens"; import { history } from "./index"; const AppRouter: React.FC = () => (
diff --git a/frontend/src/components/atoms/FloatingCard.tsx b/frontend/src/components/atoms/FloatingCard.tsx index 53f928c..938618b 100644 --- a/frontend/src/components/atoms/FloatingCard.tsx +++ b/frontend/src/components/atoms/FloatingCard.tsx @@ -11,7 +11,7 @@ const FloatingCardElement = styled(Card)` z-index: 2; `; -const FloatingCard: React.FC = props => ( +const FloatingCard: React.FC = (props) => ( diff --git a/frontend/src/components/atoms/GraphHideEdgesButton.tsx b/frontend/src/components/atoms/GraphHideEdgesButton.tsx index f901b44..3453bc4 100644 --- a/frontend/src/components/atoms/GraphHideEdgesButton.tsx +++ b/frontend/src/components/atoms/GraphHideEdgesButton.tsx @@ -7,11 +7,11 @@ const StyledSwitch = styled(Switch)` margin: 0; `; -interface IGraphHideEdgesButtonProps { +interface GraphHideEdgesButtonProps { isShowingEdges: boolean; toggleEdges: () => void; } -const GraphHideEdgesButton: React.FC = ({ isShowingEdges, toggleEdges }) => ( +const GraphHideEdgesButton: React.FC = ({ isShowingEdges, toggleEdges }) => ( diff --git a/frontend/src/components/atoms/GraphKey.tsx b/frontend/src/components/atoms/GraphKey.tsx index 10d15f0..d6272f2 100644 --- a/frontend/src/components/atoms/GraphKey.tsx +++ b/frontend/src/components/atoms/GraphKey.tsx @@ -6,9 +6,9 @@ import React from "react"; import styled from "styled-components"; import { FloatingCard, InstanceType } from "."; import { QUANTITATIVE_COLOR_SCHEME } from "../../constants"; -import { IColorScheme } from "../../types"; +import { ColorScheme } from "../../types"; -const ColorSchemeSelect = Select.ofType(); +const ColorSchemeSelect = Select.ofType(); const StyledLi = styled.li` margin-top: 2px; @@ -27,12 +27,12 @@ const ColorBarContainer = styled.div` flex-direction: column; margin-right: 10px; `; -interface IColorBarProps { +interface ColorBarProps { color: string; } -const ColorBar = styled.div` +const ColorBar = styled.div` width: 10px; - background-color: ${props => props.color}; + background-color: ${(props) => props.color}; flex: 1; `; const TextContainer = styled.div` @@ -41,13 +41,46 @@ const TextContainer = styled.div` justify-content: space-between; `; -interface IGraphKeyProps { - current?: IColorScheme; - colorSchemes: IColorScheme[]; +const renderItem: ItemRenderer = (colorScheme, { handleClick, modifiers }) => { + if (!modifiers.matchesPredicate) { + return null; + } + return ; +}; + +const renderQualitativeKey = (values: string[]) => ( +
    + {values.map((v) => ( + + + + ))} +
+); + +const renderQuantitativeKey = (range: number[]) => { + const [min, max] = range; + return ( + + + {QUANTITATIVE_COLOR_SCHEME.map((color) => ( + + ))} + + + {numeral.default(min).format("0")} + {numeral.default(max).format("0")} + + + ); +}; +interface GraphKeyProps { + current?: ColorScheme; + colorSchemes: ColorScheme[]; ranges?: { [key: string]: [number, number] }; - onItemSelect: (colorScheme?: IColorScheme) => void; + onItemSelect: (colorScheme?: ColorScheme) => void; } -const GraphKey: React.FC = ({ current, colorSchemes, ranges, onItemSelect }) => { +const GraphKey: React.FC = ({ current, colorSchemes, ranges, onItemSelect }) => { const unsetColorScheme = () => { onItemSelect(undefined); }; @@ -76,13 +109,7 @@ const GraphKey: React.FC = ({ current, colorSchemes, ranges, onI rightIcon={IconNames.CARET_DOWN} tabIndex={-1} /> - {isEndOfSection && ( - )} @@ -187,20 +188,19 @@ class InstanceTable extends React.PureComponent { const { instanceListSort } = this.props; if (instanceListSort.field !== field) { return IconNames.SORT; - } else if (instanceListSort.direction === "asc") { - return IconNames.SORT_ASC; - } else { - return IconNames.SORT_DESC; } + if (instanceListSort.direction === "asc") { + return IconNames.SORT_ASC; + } + return IconNames.SORT_DESC; }; private getSortIntent = (field: SortField) => { const { instanceListSort } = this.props; if (instanceListSort.field === field) { return Intent.PRIMARY; - } else { - return Intent.NONE; } + return Intent.NONE; }; private getPagesToDisplay = (totalPages: number, currentPage: number) => { @@ -214,24 +214,19 @@ class InstanceTable extends React.PureComponent { const pagesToDisplay = firstPages.concat(surroundingPages).concat(lastPages); - return sortedUniq(sortBy(pagesToDisplay, n => n)); + return sortedUniq(sortBy(pagesToDisplay, (n) => n)); }; } -const mapStateToProps = (state: IAppState) => { - return { - instanceListSort: state.data.instanceListSort, - instancesResponse: state.data.instancesResponse, - isLoading: state.data.isLoadingInstanceList, - loadError: state.data.instanceListLoadError - }; -}; +const mapStateToProps = (state: AppState) => ({ + instanceListSort: state.data.instanceListSort, + instancesResponse: state.data.instancesResponse, + isLoading: state.data.isLoadingInstanceList, + loadError: state.data.instanceListLoadError, +}); const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadInstanceList: (page?: number, sort?: IInstanceSort) => dispatch(loadInstanceList(page, sort) as any), - navigate: (path: string) => dispatch(push(path)) + loadInstanceList: (page?: number, sort?: InstanceSort) => dispatch(loadInstanceList(page, sort) as any), + navigate: (path: string) => dispatch(push(path)), }); -export default connect( - mapStateToProps, - mapDispatchToProps -)(InstanceTable); +export default connect(mapStateToProps, mapDispatchToProps)(InstanceTable); diff --git a/frontend/src/components/organisms/Nav.tsx b/frontend/src/components/organisms/Nav.tsx index d6e4d39..b19caf0 100644 --- a/frontend/src/components/organisms/Nav.tsx +++ b/frontend/src/components/organisms/Nav.tsx @@ -1,21 +1,19 @@ import * as React from "react"; -import { Alignment, Navbar } from "@blueprintjs/core"; +import { Alignment, Navbar, Classes } from "@blueprintjs/core"; import { IconNames } from "@blueprintjs/icons"; -import { Classes } from "@blueprintjs/core"; import { match, NavLink } from "react-router-dom"; -import { IInstanceDomainPath } from "../../constants"; +import { InstanceDomainPath } from "../../constants"; -interface INavState { +interface NavState { aboutIsOpen: boolean; } -const graphIsActive = (currMatch: match, location: Location) => { - return location.pathname === "/" || location.pathname.startsWith("/instance/"); -}; +const graphIsActive = (currMatch: match, location: Location) => + location.pathname === "/" || location.pathname.startsWith("/instance/"); -class Nav extends React.Component<{}, INavState> { +class Nav extends React.Component<{}, NavState> { constructor(props: any) { super(props); this.state = { aboutIsOpen: false }; @@ -23,7 +21,7 @@ class Nav extends React.Component<{}, INavState> { public render() { return ( - + fediverse.space @@ -46,7 +44,7 @@ class Nav extends React.Component<{}, INavState> { to="/about" className={`${Classes.BUTTON} ${Classes.MINIMAL} bp3-icon-${IconNames.INFO_SIGN}`} activeClassName={Classes.INTENT_PRIMARY} - exact={true} + exact > About diff --git a/frontend/src/components/organisms/SearchFilters.tsx b/frontend/src/components/organisms/SearchFilters.tsx index 190e729..fd6a9c2 100644 --- a/frontend/src/components/organisms/SearchFilters.tsx +++ b/frontend/src/components/organisms/SearchFilters.tsx @@ -3,7 +3,7 @@ import { IconNames } from "@blueprintjs/icons"; import React, { MouseEvent } from "react"; import styled from "styled-components"; import { INSTANCE_TYPES } from "../../constants"; -import { getSearchFilterDisplayValue, ISearchFilter } from "../../searchFilters"; +import { getSearchFilterDisplayValue, SearchFilter } from "../../searchFilters"; import { getTypeDisplayString } from "../../util"; const SearchFilterContainer = styled.div` @@ -19,30 +19,30 @@ const StyledTag = styled(Tag)` margin-left: 5px; `; -interface ISearchFiltersProps { - selectedFilters: ISearchFilter[]; - selectFilter: (filter: ISearchFilter) => void; +interface SearchFiltersProps { + selectedFilters: SearchFilter[]; + selectFilter: (filter: SearchFilter) => void; deselectFilter: (e: MouseEvent, props: ITagProps) => void; } -const SearchFilters: React.FC = ({ selectedFilters, selectFilter, deselectFilter }) => { - const hasInstanceTypeFilter = selectedFilters.some(sf => sf.field === "type"); +const SearchFilters: React.FC = ({ selectedFilters, selectFilter, deselectFilter }) => { + const hasInstanceTypeFilter = selectedFilters.some((sf) => sf.field === "type"); const handleSelectInstanceType = (e: MouseEvent) => { const field = "type"; const relation = "eq"; const value = e.currentTarget.innerText.toLowerCase().replace(" ", ""); - const filter: ISearchFilter = { + const filter: SearchFilter = { displayValue: getSearchFilterDisplayValue(field, relation, value), field, relation, - value + value, }; selectFilter(filter); }; const renderMenu = () => ( - {INSTANCE_TYPES.map(t => ( + {INSTANCE_TYPES.map((t) => ( ))} @@ -51,15 +51,15 @@ const SearchFilters: React.FC = ({ selectedFilters, selectF return ( - {selectedFilters.map(filter => ( - + {selectedFilters.map((filter) => ( + {filter.displayValue} ))} - diff --git a/frontend/src/components/organisms/SidebarContainer.tsx b/frontend/src/components/organisms/SidebarContainer.tsx index 6af77a4..c9f882c 100644 --- a/frontend/src/components/organisms/SidebarContainer.tsx +++ b/frontend/src/components/organisms/SidebarContainer.tsx @@ -17,11 +17,9 @@ const StyledCard = styled(Card)` display: flex; flex-direction: column; `; -const SidebarContainer: React.FC = ({ children }) => { - return ( - - {children} - - ); -}; +const SidebarContainer: React.FC = ({ children }) => ( + + {children} + +); export default SidebarContainer; diff --git a/frontend/src/components/screens/AboutScreen.tsx b/frontend/src/components/screens/AboutScreen.tsx index fad79c0..319b167 100644 --- a/frontend/src/components/screens/AboutScreen.tsx +++ b/frontend/src/components/screens/AboutScreen.tsx @@ -2,9 +2,9 @@ import { Classes, Code, H1, H2, H4 } from "@blueprintjs/core"; import * as React from "react"; import styled from "styled-components"; // import appsignalLogo from "../../assets/appsignal.svg"; -import gitlabLogo from "../../assets/gitlab.png"; -import nlnetLogo from "../../assets/nlnet.png"; -import { Page } from "../atoms/"; +import * as gitlabLogo from "../../assets/gitlab.png"; +import * as nlnetLogo from "../../assets/nlnet.png"; +import { Page } from "../atoms"; const SponsorContainer = styled.div` margin-bottom: 20px; @@ -36,10 +36,11 @@ const AboutScreen: React.FC = () => (

FAQ

-

Why can't I see details about my instance?

+

Why can't I see details about my instance?

fediverse.space only supports servers using the Mastodon API, the Misskey API, the GNU Social API, or Nodeinfo. - Instances with 10 or fewer users won't be scraped -- it's a tool for understanding communities, not individuals. + Instances with 10 or fewer users won't be scraped -- it's a tool for understanding communities, not + individuals.

diff --git a/frontend/src/components/screens/AdminScreen.tsx b/frontend/src/components/screens/AdminScreen.tsx index feb122a..52e0821 100644 --- a/frontend/src/components/screens/AdminScreen.tsx +++ b/frontend/src/components/screens/AdminScreen.tsx @@ -17,7 +17,7 @@ const ButtonContainer = styled.div` justify-content: space-between; `; -interface IAdminSettings { +interface AdminSettings { domain: string; optIn: boolean; optOut: boolean; @@ -25,26 +25,26 @@ interface IAdminSettings { statusCount: number; } -interface IAdminScreenProps { +interface AdminScreenProps { navigate: (path: string) => void; } -interface IAdminScreenState { - settings?: IAdminSettings; +interface AdminScreenState { + settings?: AdminSettings; isUpdating: boolean; } -class AdminScreen extends React.PureComponent { +class AdminScreen extends React.PureComponent { private authToken = getAuthToken(); - public constructor(props: IAdminScreenProps) { + public constructor(props: AdminScreenProps) { super(props); this.state = { isUpdating: false }; } public componentDidMount() { // Load instance settings from server - if (!!this.authToken) { - getFromApi(`admin`, this.authToken!) - .then(response => { + if (this.authToken) { + getFromApi(`admin`, this.authToken) + .then((response) => { this.setState({ settings: response }); }) .catch(() => { @@ -52,7 +52,7 @@ class AdminScreen extends React.PureComponent) => { - const settings = this.state.settings as IAdminSettings; + const settings = this.state.settings as AdminSettings; const optIn = e.currentTarget.checked; - let optOut = settings.optOut; + let { optOut } = settings; if (optIn) { optOut = false; } @@ -126,9 +126,9 @@ class AdminScreen extends React.PureComponent) => { - const settings = this.state.settings as IAdminSettings; + const settings = this.state.settings as AdminSettings; const optOut = e.currentTarget.checked; - let optIn = settings.optIn; + let { optIn } = settings; if (optOut) { optIn = false; } @@ -140,15 +140,15 @@ class AdminScreen extends React.PureComponent { + .then((response) => { this.setState({ settings: response, isUpdating: false }); AppToaster.show({ icon: IconNames.TICK, intent: Intent.SUCCESS, - message: "Successfully updated settings." + message: "Successfully updated settings.", }); }) .catch(() => { @@ -161,16 +161,13 @@ class AdminScreen extends React.PureComponent ({ - navigate: (path: string) => dispatch(push(path)) + navigate: (path: string) => dispatch(push(path)), }); -export default connect( - undefined, - mapDispatchToProps -)(AdminScreen); +export default connect(undefined, mapDispatchToProps)(AdminScreen); diff --git a/frontend/src/components/screens/GraphScreen.tsx b/frontend/src/components/screens/GraphScreen.tsx index 85ea9c4..2fb5fd0 100644 --- a/frontend/src/components/screens/GraphScreen.tsx +++ b/frontend/src/components/screens/GraphScreen.tsx @@ -7,9 +7,9 @@ import { Route, RouteComponentProps, Switch, withRouter } from "react-router"; import { InstanceScreen, SearchScreen } from "."; import { INSTANCE_DOMAIN_PATH } from "../../constants"; import { loadInstance } from "../../redux/actions"; -import { IAppState } from "../../redux/types"; +import { AppState } from "../../redux/types"; import { domainMatchSelector, isSmallScreen } from "../../util"; -import { Graph, SidebarContainer } from "../organisms/"; +import { Graph, SidebarContainer } from "../organisms"; const GraphContainer = styled.div` display: flex; @@ -24,13 +24,13 @@ const FullDiv = styled.div` right: 0; `; -interface IGraphScreenProps extends RouteComponentProps { +interface GraphScreenProps extends RouteComponentProps { currentInstanceName: string | null; pathname: string; graphLoadError: boolean; loadInstance: (domain: string | null) => void; } -interface IGraphScreenState { +interface GraphScreenState { hasBeenViewed: boolean; } /** @@ -41,8 +41,8 @@ interface IGraphScreenState { * However, if it's not the first page viewed (e.g. if someone opens directly on /about) we don't want to render the * graph since it slows down everything else! */ -class GraphScreenImpl extends React.Component { - public constructor(props: IGraphScreenProps) { +class GraphScreenImpl extends React.Component { + public constructor(props: GraphScreenProps) { super(props); this.state = { hasBeenViewed: false }; } @@ -56,7 +56,7 @@ class GraphScreenImpl extends React.Component ( + private renderRoutes = () => ( {/* Smaller screens never load the entire graph. Instead, `InstanceScreen` shows only the neighborhood. */} @@ -80,7 +80,7 @@ class GraphScreenImpl extends React.Component - + @@ -94,19 +94,16 @@ class GraphScreenImpl extends React.Component { +const mapStateToProps = (state: AppState) => { const match = domainMatchSelector(state); return { currentInstanceName: match && match.params.domain, graphLoadError: state.data.graphLoadError, - pathname: state.router.location.pathname + pathname: state.router.location.pathname, }; }; const mapDispatchToProps = (dispatch: Dispatch) => ({ - loadInstance: (domain: string | null) => dispatch(loadInstance(domain) as any) + loadInstance: (domain: string | null) => dispatch(loadInstance(domain) as any), }); -const GraphScreen = connect( - mapStateToProps, - mapDispatchToProps -)(GraphScreenImpl); +const GraphScreen = connect(mapStateToProps, mapDispatchToProps)(GraphScreenImpl); export default withRouter(GraphScreen); diff --git a/frontend/src/components/screens/InstanceScreen.tsx b/frontend/src/components/screens/InstanceScreen.tsx index 0991faf..4c2b2a2 100644 --- a/frontend/src/components/screens/InstanceScreen.tsx +++ b/frontend/src/components/screens/InstanceScreen.tsx @@ -20,7 +20,7 @@ import { Spinner, Tab, Tabs, - Tooltip + Tooltip, } from "@blueprintjs/core"; import { IconNames } from "@blueprintjs/icons"; @@ -28,10 +28,10 @@ import { push } from "connected-react-router"; import { Link } from "react-router-dom"; import { Dispatch } from "redux"; import styled from "styled-components"; -import { IAppState, IGraph, IGraphResponse, IInstanceDetails } from "../../redux/types"; +import { AppState, Graph, GraphResponse, InstanceDetails, Peer } from "../../redux/types"; import { domainMatchSelector, getFromApi, isSmallScreen } from "../../util"; import { InstanceType } from "../atoms"; -import { Cytoscape, ErrorState } from "../molecules/"; +import { Cytoscape, ErrorState } from "../molecules"; import { FederationTab } from "../organisms"; const InstanceScreenContainer = styled.div` @@ -82,25 +82,25 @@ const StyledGraphContainer = styled.div` flex-direction: column; margin-bottom: 10px; `; -interface IInstanceScreenProps { - graph?: IGraph; +interface InstanceScreenProps { + graph?: Graph; instanceName: string | null; instanceLoadError: boolean; - instanceDetails: IInstanceDetails | null; + instanceDetails: InstanceDetails | null; isLoadingInstanceDetails: boolean; navigateToRoot: () => void; navigateToInstance: (domain: string) => void; } -interface IInstanceScreenState { +interface InstanceScreenState { neighbors?: string[]; isProcessingNeighbors: boolean; // Local (neighborhood) graph. Used only on small screens (mobile devices). isLoadingLocalGraph: boolean; - localGraph?: IGraph; + localGraph?: Graph; localGraphLoadError?: boolean; } -class InstanceScreenImpl extends React.PureComponent { - public constructor(props: IInstanceScreenProps) { +class InstanceScreenImpl extends React.PureComponent { + public constructor(props: InstanceScreenProps) { super(props); this.state = { isProcessingNeighbors: false, isLoadingLocalGraph: false, localGraphLoadError: false }; } @@ -116,9 +116,9 @@ class InstanceScreenImpl extends React.PureComponent; - } else if (this.props.instanceDetails.status.toLowerCase().indexOf("personal instance") > -1) { + } else if (this.props.instanceDetails.status.toLowerCase().includes("personal instance")) { content = this.renderPersonalInstanceErrorState(); - } else if (this.props.instanceDetails.status.toLowerCase().indexOf("robots.txt") > -1) { + } else if (this.props.instanceDetails.status.toLowerCase().includes("robots.txt")) { content = this.renderRobotsTxtState(); } else if (this.props.instanceDetails.status !== "success") { content = this.renderMissingDataState(); @@ -130,7 +130,7 @@ class InstanceScreenImpl extends React.PureComponent {this.props.instanceName} - + @@ -145,7 +145,7 @@ class InstanceScreenImpl extends React.PureComponent [e.data.source, e.data.target].indexOf(instanceName!) > -1); + const graphToUse = graph || localGraph; + if (!graphToUse) { + return; + } + const edges = graphToUse.edges.filter((e) => [e.data.source, e.data.target].includes(instanceName)); const neighbors: any[] = []; - edges.forEach(e => { + edges.forEach((e) => { if (e.data.source === instanceName) { neighbors.push({ neighbor: e.data.target, weight: e.data.weight }); } else { @@ -183,39 +186,38 @@ class InstanceScreenImpl extends React.PureComponent { + .then((response: GraphResponse) => { // We do some processing of edges here to make sure that every edge's source and target are in the neighborhood // We could (and should) be doing this in the backend, but I don't want to mess around with complex SQL // queries. // TODO: think more about moving the backend to a graph database that would make this easier. - const graph = response.graph; - const nodeIds = new Set(graph.nodes.map(n => n.data.id)); - const edges = graph.edges.filter(e => nodeIds.has(e.data.source) && nodeIds.has(e.data.target)); + const { graph } = response; + const nodeIds = new Set(graph.nodes.map((n) => n.data.id)); + const edges = graph.edges.filter((e) => nodeIds.has(e.data.source) && nodeIds.has(e.data.target)); this.setState({ isLoadingLocalGraph: false, localGraph: { ...graph, edges } }); }) .catch(() => this.setState({ isLoadingLocalGraph: false, localGraphLoadError: true })); }; private renderTabs = () => { + const { instanceDetails } = this.props; const hasNeighbors = this.state.neighbors && this.state.neighbors.length > 0; - const federationRestrictions = this.props.instanceDetails && this.props.instanceDetails.federationRestrictions; + const federationRestrictions = instanceDetails && instanceDetails.federationRestrictions; const hasLocalGraph = !!this.state.localGraph && this.state.localGraph.nodes.length > 0 && this.state.localGraph.edges.length > 0; const insularCallout = this.props.graph && !this.state.isProcessingNeighbors && !hasNeighbors && !hasLocalGraph ? ( -

This instance doesn't have any neighbors that we know of, so it's hidden from the graph.

+

This instance doesn't have any neighbors that we know of, so it's hidden from the graph.

- ) : ( - undefined - ); + ) : undefined; return ( <> {insularCallout} {this.maybeRenderLocalGraph()} - {this.props.instanceDetails!.description && ( + {instanceDetails && instanceDetails.description && ( )} {this.shouldRenderStats() && } @@ -232,7 +234,7 @@ class InstanceScreenImpl extends React.PureComponent { const { localGraph } = this.state; - const hasLocalGraph = - !!this.state.localGraph && this.state.localGraph.nodes.length > 0 && this.state.localGraph.edges.length > 0; - if (!hasLocalGraph) { + const hasLocalGraph = !!localGraph && localGraph.nodes.length > 0 && localGraph.edges.length > 0; + if (!hasLocalGraph || !localGraph) { return; } return ( - + @@ -268,7 +269,11 @@ class InstanceScreenImpl extends React.PureComponent { - const description = this.props.instanceDetails!.description; + const { instanceDetails } = this.props; + if (!instanceDetails) { + return; + } + const { description } = instanceDetails; if (!description) { return; } @@ -288,10 +293,10 @@ class InstanceScreenImpl extends React.PureComponent + Version @@ -299,7 +304,7 @@ class InstanceScreenImpl extends React.PureComponent Instance type - {(type && ) || "Unknown"} + {(type && ) || "Unknown"} Users @@ -311,7 +316,8 @@ class InstanceScreenImpl extends React.PureComponent - Insularity{" "} + Insularity + {" "} @@ -330,7 +336,8 @@ class InstanceScreenImpl extends React.PureComponent - Statuses / day{" "} + Statuses / day + {" "} @@ -349,7 +356,8 @@ class InstanceScreenImpl extends React.PureComponent - Statuses / person / day{" "} + Statuses / person / day + {" "} @@ -372,7 +380,7 @@ class InstanceScreenImpl extends React.PureComponent Last updated - {moment(lastUpdated + "Z").fromNow() || "Unknown"} + {moment(`${lastUpdated}Z`).fromNow() || "Unknown"} @@ -412,7 +420,7 @@ class InstanceScreenImpl extends React.PureComponent - + Instance @@ -426,11 +434,15 @@ class InstanceScreenImpl extends React.PureComponent { - const peers = this.props.instanceDetails!.peers; + const { instanceDetails } = this.props; + if (!instanceDetails) { + return; + } + const { peers } = instanceDetails; if (!peers || peers.length === 0) { return; } - const peerRows = peers.map(instance => ( + const peerRows = peers.map((instance: Peer) => ( @@ -444,7 +456,7 @@ class InstanceScreenImpl extends React.PureComponent All the instances, past and present, that {this.props.instanceName} knows about.

- + {peerRows} @@ -453,71 +465,62 @@ class InstanceScreenImpl extends React.PureComponent } />; - private renderPersonalInstanceErrorState = () => { - return ( - - {"Opt in"} - - } - /> - ); - }; + private renderPersonalInstanceErrorState = () => ( + + Opt in + + } + /> + ); - private renderMissingDataState = () => { - return ( - <> - - - {this.props.instanceDetails && this.props.instanceDetails.status} + private renderMissingDataState = () => ( + <> + + + {this.props.instanceDetails && this.props.instanceDetails.status} + + + ); + + private renderRobotsTxtState = () => ( + + 🤖 - - ); - }; - - private renderRobotsTxtState = () => { - return ( - - 🤖 - - } - title="No data" - description="This instance was not crawled because its robots.txt did not allow us to." - /> - ); - }; + } + title="No data" + description="This instance was not crawled because its robots.txt did not allow us to." + /> + ); private openInstanceLink = () => { - window.open("https://" + this.props.instanceName, "_blank"); + window.open(`https://${this.props.instanceName}`, "_blank"); }; } -const mapStateToProps = (state: IAppState) => { +const mapStateToProps = (state: AppState) => { const match = domainMatchSelector(state); return { graph: state.data.graphResponse && state.data.graphResponse.graph, instanceDetails: state.currentInstance.currentInstanceDetails, instanceLoadError: state.currentInstance.error, instanceName: match && match.params.domain, - isLoadingInstanceDetails: state.currentInstance.isLoadingInstanceDetails + isLoadingInstanceDetails: state.currentInstance.isLoadingInstanceDetails, }; }; const mapDispatchToProps = (dispatch: Dispatch) => ({ navigateToInstance: (domain: string) => dispatch(push(`/instance/${domain}`)), - navigateToRoot: () => dispatch(push("/")) + navigateToRoot: () => dispatch(push("/")), }); -const InstanceScreen = connect( - mapStateToProps, - mapDispatchToProps -)(InstanceScreenImpl); +const InstanceScreen = connect(mapStateToProps, mapDispatchToProps)(InstanceScreenImpl); export default InstanceScreen; diff --git a/frontend/src/components/screens/LoginScreen.tsx b/frontend/src/components/screens/LoginScreen.tsx index 57f0569..a33c27a 100644 --- a/frontend/src/components/screens/LoginScreen.tsx +++ b/frontend/src/components/screens/LoginScreen.tsx @@ -8,11 +8,11 @@ import { getAuthToken, getFromApi, postToApi } from "../../util"; import { Page } from "../atoms"; import { ErrorState } from "../molecules"; -interface IFormContainerProps { +interface FormContainerProps { error: boolean; } -const FormContainer = styled.div` - ${props => (props.error ? "margin: 20px auto 0 auto;" : "margin-top: 20px;")} +const FormContainer = styled.div` + ${(props) => (props.error ? "margin: 20px auto 0 auto;" : "margin-top: 20px;")} `; const LoginTypeContainer = styled.div` display: flex; @@ -31,23 +31,28 @@ const StyledIcon = styled(Icon)` margin-bottom: 10px; `; -interface ILoginTypes { +interface LoginTypes { domain: string; email?: string; fediverseAccount?: string; } -interface ILoginScreenState { +interface LoginScreenState { domain: string; isGettingLoginTypes: boolean; isSendingLoginRequest: boolean; - loginTypes?: ILoginTypes; + loginTypes?: LoginTypes; selectedLoginType?: "email" | "fediverseAccount"; error: boolean; } -class LoginScreen extends React.PureComponent<{}, ILoginScreenState> { +class LoginScreen extends React.PureComponent<{}, LoginScreenState> { public constructor(props: any) { super(props); - this.state = { domain: "", error: false, isGettingLoginTypes: false, isSendingLoginRequest: false }; + this.state = { + domain: "", + error: false, + isGettingLoginTypes: false, + isSendingLoginRequest: false, + }; } public render() { @@ -59,13 +64,13 @@ class LoginScreen extends React.PureComponent<{}, ILoginScreenState> { const { error, loginTypes, isSendingLoginRequest, selectedLoginType } = this.state; let content; - if (!!error) { + if (error) { content = ( ); } else if (!!selectedLoginType && !isSendingLoginRequest) { content = this.renderPostLogin(); - } else if (!!loginTypes) { + } else if (loginTypes) { content = this.renderChooseLoginType(); } else { content = this.renderChooseInstance(); @@ -78,7 +83,7 @@ class LoginScreen extends React.PureComponent<{}, ILoginScreenState> { You must be the instance admin to manage how fediverse.space interacts with your instance.

- It's currently only possible to administrate Mastodon and Pleroma instances. If you want to login with a + It's currently only possible to administrate Mastodon and Pleroma instances. If you want to login with a direct message, your instance must federate with mastodon.social and vice versa.

@@ -95,7 +100,7 @@ class LoginScreen extends React.PureComponent<{}, ILoginScreenState> { const onButtonClick = () => this.getLoginTypes(); return (

- + { rightElement={ )} @@ -104,13 +104,11 @@ class SearchScreen extends React.PureComponent; } else if (query || error) { - rightSearchBarElement = ( -