Full basic support
This commit is contained in:
parent
e388d89494
commit
488425ce75
2
.env
2
.env
@ -1,2 +1,2 @@
|
|||||||
DATABASE_URL=postgres://postgres@localhost:5432/jirs
|
DATABASE_URL=postgres://postgres@localhost:5432/jirs
|
||||||
|
RUST_LOG=actix_web=info,diesel=info
|
||||||
|
244
Cargo.lock
generated
244
Cargo.lock
generated
@ -329,6 +329,18 @@ dependencies = [
|
|||||||
"memchr 2.3.3",
|
"memchr 2.3.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "013a6e0a2cbe3d20f9c60b65458f7a7f7a5e636c5d0f45a5a6aee5d4b1f01785"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anymap"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arc-swap"
|
name = "arc-swap"
|
||||||
version = "0.4.5"
|
version = "0.4.5"
|
||||||
@ -408,6 +420,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base-x"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b20b618342cf9891c292c4f5ac2cde7287cc5c87e87e9c769d617793607dec1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@ -425,12 +443,28 @@ dependencies = [
|
|||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "boolinator"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "brotli-sys"
|
name = "brotli-sys"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -451,6 +485,12 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.3.4"
|
version = "1.3.4"
|
||||||
@ -484,6 +524,12 @@ version = "0.1.10"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-match"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8100e46ff92eb85bf6dc2930c73f2a4f7176393c84a9446b3d501e1b354e7b34"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
@ -587,6 +633,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "discard"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dotenv"
|
name = "dotenv"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
@ -959,6 +1011,9 @@ version = "0.1.0"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "jirs-client"
|
name = "jirs-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"yew",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jirs-data"
|
name = "jirs-data"
|
||||||
@ -1497,6 +1552,15 @@ version = "0.1.16"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc_version"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||||
|
dependencies = [
|
||||||
|
"semver",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
@ -1518,6 +1582,21 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||||
|
dependencies = [
|
||||||
|
"semver-parser",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver-parser"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.105"
|
version = "1.0.105"
|
||||||
@ -1601,6 +1680,57 @@ dependencies = [
|
|||||||
"winapi 0.3.8",
|
"winapi 0.3.8",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stdweb"
|
||||||
|
version = "0.4.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
|
||||||
|
dependencies = [
|
||||||
|
"discard",
|
||||||
|
"rustc_version",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"stdweb-derive",
|
||||||
|
"stdweb-internal-macros",
|
||||||
|
"stdweb-internal-runtime",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stdweb-derive"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stdweb-internal-macros"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
|
||||||
|
dependencies = [
|
||||||
|
"base-x",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"sha1",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stdweb-internal-runtime"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.17"
|
version = "1.0.17"
|
||||||
@ -1633,6 +1763,26 @@ dependencies = [
|
|||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3711fd1c4e75b3eff12ba5c40dba762b6b65c5476e8174c1a664772060c49bf"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae2b85ba4c9aa32dd3343bd80eb8d22e9b54b7688c17ea3907f236885353b233"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread-id"
|
name = "thread-id"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@ -1832,6 +1982,60 @@ version = "0.9.0+wasi-snapshot-preview1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"lazy_static",
|
||||||
|
"log 0.4.8",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "widestring"
|
name = "widestring"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@ -1899,3 +2103,43 @@ dependencies = [
|
|||||||
"winapi 0.2.8",
|
"winapi 0.2.8",
|
||||||
"winapi-build",
|
"winapi-build",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yew"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "20c1d343cdf3d625752fd4d9fef73e1b6b2b8a08b00c832b0fc4a47f3b10fbef"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"anymap",
|
||||||
|
"bincode",
|
||||||
|
"cfg-if",
|
||||||
|
"cfg-match",
|
||||||
|
"http",
|
||||||
|
"indexmap",
|
||||||
|
"log 0.4.8",
|
||||||
|
"proc-macro-hack",
|
||||||
|
"proc-macro-nested",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"slab",
|
||||||
|
"stdweb",
|
||||||
|
"thiserror",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"yew-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yew-macro"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fb25d91f16265dd1643497ac3252be6c21c169d9cf3babcae32325c74200499f"
|
||||||
|
dependencies = [
|
||||||
|
"boolinator",
|
||||||
|
"lazy_static",
|
||||||
|
"proc-macro-hack",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
3
jirs-client/.gitignore
vendored
Normal file
3
jirs-client/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pkg
|
||||||
|
node_modules
|
||||||
|
|
@ -5,7 +5,9 @@ authors = ["Adrian Wozniak <adrian.wozniak@ita-prog.pl>"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
name = "jirs_client"
|
name = "jirs_client"
|
||||||
path = "./src/lib.rs"
|
path = "./src/lib.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
yew = { version = "*", features = ["std_web"] }
|
||||||
|
1
jirs-client/js/index.js
Normal file
1
jirs-client/js/index.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
import("../pkg/index");
|
11
jirs-client/package.json
Normal file
11
jirs-client/package.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@swc/core": "^1.1.37",
|
||||||
|
"@wasm-tool/wasm-pack-plugin": "^1.2.0",
|
||||||
|
"html-webpack-plugin": "^4.0.3",
|
||||||
|
"swc-loader": "^0.1.8",
|
||||||
|
"webpack": "^4.42.1",
|
||||||
|
"webpack-cli": "^3.3.11",
|
||||||
|
"webpack-dev-server": "^3.10.3"
|
||||||
|
}
|
||||||
|
}
|
@ -1 +1,47 @@
|
|||||||
|
use yew::{html, Callback, ClickEvent, Component, ComponentLink, Html, ShouldRender};
|
||||||
|
|
||||||
|
struct App {
|
||||||
|
clicked: bool,
|
||||||
|
onclick: Callback<ClickEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Msg {
|
||||||
|
Click,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for App {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
|
||||||
|
App {
|
||||||
|
clicked: false,
|
||||||
|
onclick: link.callback(|_| Msg::Click),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||||
|
match msg {
|
||||||
|
Msg::Click => {
|
||||||
|
self.clicked = true;
|
||||||
|
true // Indicate that the Component should re-render
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self) -> Html {
|
||||||
|
let button_text = if self.clicked {
|
||||||
|
"Clicked!"
|
||||||
|
} else {
|
||||||
|
"Click me!"
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<button onclick=&self.onclick>{ button_text }</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
yew::start_app::<App>();
|
||||||
|
}
|
||||||
|
35
jirs-client/webpack.config.js
Normal file
35
jirs-client/webpack.config.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
|
||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: path.resolve(__dirname, 'js', 'index.js'),
|
||||||
|
output: {
|
||||||
|
filename: '[name].js',
|
||||||
|
path: path.resolve(__dirname, 'dev'),
|
||||||
|
publicPath: '/',
|
||||||
|
},
|
||||||
|
devtool: 'source-map',
|
||||||
|
devServer: {
|
||||||
|
contentBase: path.join(__dirname, 'dev'),
|
||||||
|
historyApiFallback: true,
|
||||||
|
hot: true,
|
||||||
|
port: 4000,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
allowedHosts: [
|
||||||
|
'localhost:4000',
|
||||||
|
'localhost:8000',
|
||||||
|
],
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new WasmPackPlugin({
|
||||||
|
crateDirectory: path.resolve(__dirname),
|
||||||
|
args: "--log-level warn",
|
||||||
|
extraArgs: "--no-typescript",
|
||||||
|
}),
|
||||||
|
new HtmlWebpackPlugin(),
|
||||||
|
]
|
||||||
|
};
|
4215
jirs-client/yarn.lock
Normal file
4215
jirs-client/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -171,3 +171,43 @@ pub struct UpdateIssuePayload {
|
|||||||
pub users: Option<Vec<User>>,
|
pub users: Option<Vec<User>>,
|
||||||
pub user_ids: Option<Vec<i32>>,
|
pub user_ids: Option<Vec<i32>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CreateCommentPayload {
|
||||||
|
pub user_id: Option<i32>,
|
||||||
|
pub issue_id: i32,
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct UpdateCommentPayload {
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CreateIssuePayload {
|
||||||
|
pub title: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub issue_type: String,
|
||||||
|
pub status: String,
|
||||||
|
pub priority: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub description_text: Option<String>,
|
||||||
|
pub estimate: Option<i32>,
|
||||||
|
pub time_spent: Option<i32>,
|
||||||
|
pub time_remaining: Option<i32>,
|
||||||
|
pub project_id: i32,
|
||||||
|
pub user_ids: Vec<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct UpdateProjectPayload {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub category: Option<String>,
|
||||||
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use crate::db::DbExecutor;
|
|
||||||
use crate::errors::ServiceErrors;
|
|
||||||
use crate::models::Comment;
|
|
||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::db::DbExecutor;
|
||||||
|
use crate::errors::ServiceErrors;
|
||||||
|
use crate::models::Comment;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LoadIssueComments {
|
pub struct LoadIssueComments {
|
||||||
pub issue_id: i32,
|
pub issue_id: i32,
|
||||||
@ -32,3 +33,103 @@ impl Handler<LoadIssueComments> for DbExecutor {
|
|||||||
Ok(rows)
|
Ok(rows)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CreateComment {
|
||||||
|
pub user_id: i32,
|
||||||
|
pub issue_id: i32,
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for CreateComment {
|
||||||
|
type Result = Result<Comment, ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<CreateComment> for DbExecutor {
|
||||||
|
type Result = Result<Comment, ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: CreateComment, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
use crate::models::CommentForm;
|
||||||
|
use crate::schema::comments::dsl::*;
|
||||||
|
|
||||||
|
let form = CommentForm {
|
||||||
|
body: msg.body,
|
||||||
|
user_id: msg.user_id,
|
||||||
|
issue_id: msg.issue_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let conn = &self
|
||||||
|
.0
|
||||||
|
.get()
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
let row: Comment = diesel::insert_into(comments)
|
||||||
|
.values(form)
|
||||||
|
.get_result::<Comment>(conn)
|
||||||
|
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
||||||
|
Ok(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct UpdateComment {
|
||||||
|
pub comment_id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
pub body: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for UpdateComment {
|
||||||
|
type Result = Result<Comment, ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<UpdateComment> for DbExecutor {
|
||||||
|
type Result = Result<Comment, ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: UpdateComment, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
use crate::schema::comments::dsl::*;
|
||||||
|
|
||||||
|
let conn = &self
|
||||||
|
.0
|
||||||
|
.get()
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
let row: Comment = diesel::update(
|
||||||
|
comments
|
||||||
|
.filter(user_id.eq(msg.user_id))
|
||||||
|
.find(msg.comment_id),
|
||||||
|
)
|
||||||
|
.set(body.eq(msg.body))
|
||||||
|
.get_result::<Comment>(conn)
|
||||||
|
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
||||||
|
Ok(row)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct DeleteComment {
|
||||||
|
pub comment_id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for DeleteComment {
|
||||||
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<DeleteComment> for DbExecutor {
|
||||||
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: DeleteComment, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
use crate::schema::comments::dsl::*;
|
||||||
|
|
||||||
|
let conn = &self
|
||||||
|
.0
|
||||||
|
.get()
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
diesel::delete(
|
||||||
|
comments
|
||||||
|
.filter(user_id.eq(msg.user_id))
|
||||||
|
.find(msg.comment_id),
|
||||||
|
)
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|_| ServiceErrors::RecordNotFound("issue comments".to_string()))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
use actix::{Handler, Message};
|
||||||
|
use diesel::expression::dsl::not;
|
||||||
|
use diesel::expression::sql_literal::sql;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::db::DbExecutor;
|
use crate::db::DbExecutor;
|
||||||
use crate::errors::ServiceErrors;
|
use crate::errors::ServiceErrors;
|
||||||
use crate::models::Issue;
|
use crate::models::Issue;
|
||||||
use actix::{Handler, Message};
|
|
||||||
use diesel::expression::dsl::not;
|
|
||||||
use diesel::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LoadIssue {
|
pub struct LoadIssue {
|
||||||
@ -61,7 +63,6 @@ impl Handler<LoadProjectIssues> for DbExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct UpdateIssue {
|
pub struct UpdateIssue {
|
||||||
pub issue_id: i32,
|
pub issue_id: i32,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
@ -155,3 +156,116 @@ impl Handler<UpdateIssue> for DbExecutor {
|
|||||||
Ok(row)
|
Ok(row)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct DeleteIssue {
|
||||||
|
pub issue_id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for DeleteIssue {
|
||||||
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<DeleteIssue> for DbExecutor {
|
||||||
|
type Result = Result<(), ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: DeleteIssue, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
use crate::schema::issue_assignees::dsl::{issue_assignees, issue_id};
|
||||||
|
use crate::schema::issues::dsl::issues;
|
||||||
|
|
||||||
|
let conn = &self
|
||||||
|
.0
|
||||||
|
.get()
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
|
diesel::delete(issue_assignees.filter(issue_id.eq(msg.issue_id)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| ServiceErrors::RecordNotFound(format!("issue {}. {}", msg.issue_id, e)))?;
|
||||||
|
diesel::delete(issues.find(msg.issue_id))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|e| ServiceErrors::RecordNotFound(format!("issue {}. {}", msg.issue_id, e)))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct CreateIssue {
|
||||||
|
pub title: String,
|
||||||
|
pub issue_type: String,
|
||||||
|
pub status: String,
|
||||||
|
pub priority: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub description_text: Option<String>,
|
||||||
|
pub estimate: Option<i32>,
|
||||||
|
pub time_spent: Option<i32>,
|
||||||
|
pub time_remaining: Option<i32>,
|
||||||
|
pub project_id: i32,
|
||||||
|
pub reporter_id: i32,
|
||||||
|
pub user_ids: Vec<i32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for CreateIssue {
|
||||||
|
type Result = Result<Issue, ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<CreateIssue> for DbExecutor {
|
||||||
|
type Result = Result<Issue, ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: CreateIssue, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
use crate::schema::issue_assignees::dsl;
|
||||||
|
use crate::schema::issues::dsl::{issues, status};
|
||||||
|
|
||||||
|
let conn = &self
|
||||||
|
.0
|
||||||
|
.get()
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
|
let list_position = issues
|
||||||
|
.filter(status.eq("backlog"))
|
||||||
|
.select(sql("max(list_position) + 1.0"))
|
||||||
|
.get_result::<f64>(conn)
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
|
let form = crate::models::CreateIssueForm {
|
||||||
|
title: msg.title,
|
||||||
|
issue_type: msg.issue_type,
|
||||||
|
status: msg.status,
|
||||||
|
priority: msg.priority,
|
||||||
|
list_position,
|
||||||
|
description: msg.description,
|
||||||
|
description_text: msg.description_text,
|
||||||
|
estimate: msg.estimate,
|
||||||
|
time_spent: msg.time_spent,
|
||||||
|
time_remaining: msg.time_remaining,
|
||||||
|
reporter_id: msg.reporter_id,
|
||||||
|
project_id: msg.project_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
let issue = diesel::insert_into(issues)
|
||||||
|
.values(form)
|
||||||
|
.on_conflict_do_nothing()
|
||||||
|
.get_result::<Issue>(conn)
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
|
let mut values = vec![];
|
||||||
|
for user_id in msg.user_ids.iter() {
|
||||||
|
values.push(crate::models::CreateIssueAssigneeForm {
|
||||||
|
issue_id: issue.id,
|
||||||
|
user_id: *user_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if !msg.user_ids.contains(&msg.reporter_id) {
|
||||||
|
values.push(crate::models::CreateIssueAssigneeForm {
|
||||||
|
issue_id: issue.id,
|
||||||
|
user_id: msg.reporter_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::insert_into(dsl::issue_assignees)
|
||||||
|
.values(values)
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
|
Ok(issue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3,6 +3,8 @@ use actix::{Actor, SyncContext};
|
|||||||
use diesel::pg::PgConnection;
|
use diesel::pg::PgConnection;
|
||||||
use diesel::r2d2::{self, ConnectionManager};
|
use diesel::r2d2::{self, ConnectionManager};
|
||||||
|
|
||||||
|
use crate::db::dev::VerboseConnection;
|
||||||
|
|
||||||
pub mod authorize_user;
|
pub mod authorize_user;
|
||||||
pub mod comments;
|
pub mod comments;
|
||||||
pub mod issues;
|
pub mod issues;
|
||||||
@ -27,14 +29,15 @@ impl DbExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_pool() -> DbPool {
|
pub fn build_pool() -> DbPool {
|
||||||
std::env::set_var("RUST_LOG", "actix_web=info,diesel=debug");
|
std::env::set_var("RUST_LOG", "actix_web=debug,diesel=debug");
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
|
|
||||||
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL");
|
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL");
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
let manager = ConnectionManager::<dev::VerboseConnection>::new(database_url);
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
let manager = ConnectionManager::<PgConnection>::new(database_url);
|
let manager = ConnectionManager::<PgConnection>::new(database_url.clone());
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
let manager: ConnectionManager<VerboseConnection> =
|
||||||
|
ConnectionManager::<dev::VerboseConnection>::new(database_url.clone());
|
||||||
r2d2::Pool::builder()
|
r2d2::Pool::builder()
|
||||||
.build(manager)
|
.build(manager)
|
||||||
.unwrap_or_else(|e| panic!("Failed to create pool. {}", e))
|
.unwrap_or_else(|e| panic!("Failed to create pool. {}", e))
|
||||||
@ -48,12 +51,13 @@ pub trait SyncQuery {
|
|||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
pub mod dev {
|
pub mod dev {
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use diesel::connection::{AnsiTransactionManager, SimpleConnection};
|
use diesel::connection::{AnsiTransactionManager, SimpleConnection};
|
||||||
use diesel::deserialize::QueryableByName;
|
use diesel::deserialize::QueryableByName;
|
||||||
use diesel::query_builder::{AsQuery, QueryFragment, QueryId};
|
use diesel::query_builder::{AsQuery, QueryFragment, QueryId};
|
||||||
use diesel::sql_types::HasSqlType;
|
use diesel::sql_types::HasSqlType;
|
||||||
use diesel::{Connection, ConnectionResult, PgConnection, QueryResult, Queryable};
|
use diesel::{Connection, ConnectionResult, PgConnection, QueryResult, Queryable};
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
pub struct VerboseConnection {
|
pub struct VerboseConnection {
|
||||||
inner: PgConnection,
|
inner: PgConnection,
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use crate::db::DbExecutor;
|
|
||||||
use crate::errors::ServiceErrors;
|
|
||||||
use crate::models::Project;
|
|
||||||
use actix::{Handler, Message};
|
use actix::{Handler, Message};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::db::DbExecutor;
|
||||||
|
use crate::errors::ServiceErrors;
|
||||||
|
use crate::models::Project;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LoadCurrentProject {
|
pub struct LoadCurrentProject {
|
||||||
pub project_id: i32,
|
pub project_id: i32,
|
||||||
@ -23,10 +24,50 @@ impl Handler<LoadCurrentProject> for DbExecutor {
|
|||||||
.0
|
.0
|
||||||
.get()
|
.get()
|
||||||
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
let project = projects
|
|
||||||
|
projects
|
||||||
.filter(id.eq(msg.project_id))
|
.filter(id.eq(msg.project_id))
|
||||||
.first::<Project>(conn)
|
.first::<Project>(conn)
|
||||||
.map_err(|_| ServiceErrors::RecordNotFound("Project".to_string()))?;
|
.map_err(|_| ServiceErrors::RecordNotFound("Project".to_string()))
|
||||||
Ok(project)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct UpdateProject {
|
||||||
|
pub project_id: i32,
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub url: Option<String>,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub category: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for UpdateProject {
|
||||||
|
type Result = Result<Project, ServiceErrors>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<UpdateProject> for DbExecutor {
|
||||||
|
type Result = Result<Project, ServiceErrors>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: UpdateProject, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
use crate::schema::projects::dsl::*;
|
||||||
|
let conn = &self
|
||||||
|
.0
|
||||||
|
.get()
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
|
diesel::update(projects.find(msg.project_id))
|
||||||
|
.set((
|
||||||
|
msg.name.map(|v| name.eq(v)),
|
||||||
|
msg.url.map(|v| url.eq(v)),
|
||||||
|
msg.description.map(|v| description.eq(v)),
|
||||||
|
msg.category.map(|v| category.eq(v)),
|
||||||
|
))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|_| ServiceErrors::DatabaseConnectionLost)?;
|
||||||
|
|
||||||
|
projects
|
||||||
|
.filter(id.eq(msg.project_id))
|
||||||
|
.first::<Project>(conn)
|
||||||
|
.map_err(|_| ServiceErrors::RecordNotFound("Project".to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ pub mod schema;
|
|||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_rt::main]
|
||||||
async fn main() -> Result<(), String> {
|
async fn main() -> Result<(), String> {
|
||||||
std::env::set_var("RUST_LOG", "actix_web=info,diesel=debug");
|
std::env::set_var("RUST_LOG", "actix_web=debug,diesel=debug");
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
|
|
||||||
@ -27,7 +27,6 @@ async fn main() -> Result<(), String> {
|
|||||||
.data(crate::db::build_pool())
|
.data(crate::db::build_pool())
|
||||||
.service(
|
.service(
|
||||||
web::scope("/issues")
|
web::scope("/issues")
|
||||||
.wrap(crate::middleware::authorize::Authorize::default())
|
|
||||||
.service(crate::routes::issues::project_issues)
|
.service(crate::routes::issues::project_issues)
|
||||||
.service(crate::routes::issues::issue_with_users_and_comments)
|
.service(crate::routes::issues::issue_with_users_and_comments)
|
||||||
.service(crate::routes::issues::create)
|
.service(crate::routes::issues::create)
|
||||||
@ -36,19 +35,13 @@ async fn main() -> Result<(), String> {
|
|||||||
)
|
)
|
||||||
.service(
|
.service(
|
||||||
web::scope("/comments")
|
web::scope("/comments")
|
||||||
.wrap(crate::middleware::authorize::Authorize::default())
|
|
||||||
.service(crate::routes::comments::create)
|
.service(crate::routes::comments::create)
|
||||||
.service(crate::routes::comments::update)
|
.service(crate::routes::comments::update)
|
||||||
.service(crate::routes::comments::delete),
|
.service(crate::routes::comments::delete),
|
||||||
)
|
)
|
||||||
.service(
|
.service(web::scope("/currentUser").service(crate::routes::users::current_user))
|
||||||
web::scope("/currentUser")
|
|
||||||
.wrap(crate::middleware::authorize::Authorize::default())
|
|
||||||
.service(crate::routes::users::current_user),
|
|
||||||
)
|
|
||||||
.service(
|
.service(
|
||||||
web::scope("/project")
|
web::scope("/project")
|
||||||
.wrap(crate::middleware::authorize::Authorize::default())
|
|
||||||
.service(crate::routes::projects::project_with_users_and_issues)
|
.service(crate::routes::projects::project_with_users_and_issues)
|
||||||
.service(crate::routes::projects::update),
|
.service(crate::routes::projects::update),
|
||||||
)
|
)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use crate::schema::*;
|
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::schema::*;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Comment {
|
pub struct Comment {
|
||||||
@ -34,8 +35,8 @@ impl Into<jirs_data::Comment> for Comment {
|
|||||||
#[table_name = "comments"]
|
#[table_name = "comments"]
|
||||||
pub struct CommentForm {
|
pub struct CommentForm {
|
||||||
pub body: String,
|
pub body: String,
|
||||||
pub user_id: Option<i32>,
|
pub user_id: i32,
|
||||||
pub issue_id: Option<i32>,
|
pub issue_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
||||||
@ -173,11 +174,11 @@ impl Into<jirs_data::Project> for Project {
|
|||||||
#[derive(Debug, Serialize, Deserialize, Insertable)]
|
#[derive(Debug, Serialize, Deserialize, Insertable)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[table_name = "projects"]
|
#[table_name = "projects"]
|
||||||
pub struct ProjectForm {
|
pub struct UpdateProjectForm {
|
||||||
pub name: String,
|
pub name: Option<String>,
|
||||||
pub url: String,
|
pub url: Option<String>,
|
||||||
pub description: String,
|
pub description: Option<String>,
|
||||||
pub category: String,
|
pub category: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
#[derive(Debug, Serialize, Deserialize, Queryable)]
|
||||||
|
@ -1,18 +1,77 @@
|
|||||||
use actix_web::{delete, post, put, HttpResponse};
|
use actix::Addr;
|
||||||
|
use actix_web::web::{Data, Json, Path};
|
||||||
|
use actix_web::{delete, post, put, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
#[post("/")]
|
use crate::db::comments::{CreateComment, DeleteComment, UpdateComment};
|
||||||
pub async fn create() -> HttpResponse {
|
use crate::db::DbExecutor;
|
||||||
HttpResponse::Ok()
|
use crate::routes::user_from_request;
|
||||||
.content_type("text/html")
|
|
||||||
.body("<!DOCTYPE html><html><head><title>Issues</title></head><body>Foo</body></html>")
|
#[post("")]
|
||||||
|
pub async fn create(
|
||||||
|
req: HttpRequest,
|
||||||
|
payload: Json<jirs_data::CreateCommentPayload>,
|
||||||
|
db: Data<Addr<DbExecutor>>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let user = match user_from_request(req, &db).await {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(response) => return response,
|
||||||
|
};
|
||||||
|
|
||||||
|
let msg = CreateComment {
|
||||||
|
body: payload.body.clone(),
|
||||||
|
issue_id: payload.issue_id,
|
||||||
|
user_id: user.id,
|
||||||
|
};
|
||||||
|
let comment = match db.send(msg).await {
|
||||||
|
Ok(Ok(comment)) => comment,
|
||||||
|
_ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/<id>")]
|
#[put("/{id}")]
|
||||||
pub async fn update() -> HttpResponse {
|
pub async fn update(
|
||||||
HttpResponse::Ok().content_type("text/html").body("")
|
req: HttpRequest,
|
||||||
|
path: Path<i32>,
|
||||||
|
db: Data<Addr<DbExecutor>>,
|
||||||
|
payload: Json<jirs_data::UpdateCommentPayload>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let user = match user_from_request(req, &db).await {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(response) => return response,
|
||||||
|
};
|
||||||
|
let comment_id = path.into_inner();
|
||||||
|
let body = payload.body.clone();
|
||||||
|
|
||||||
|
let msg = UpdateComment {
|
||||||
|
comment_id,
|
||||||
|
body,
|
||||||
|
user_id: user.id,
|
||||||
|
};
|
||||||
|
let comment = match db.send(msg).await {
|
||||||
|
Ok(Ok(comment)) => comment,
|
||||||
|
Ok(Err(e)) => return e.into_http_response(),
|
||||||
|
_ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(),
|
||||||
|
};
|
||||||
|
HttpResponse::Ok().json(comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/<id>")]
|
#[delete("/{id}")]
|
||||||
pub async fn delete() -> HttpResponse {
|
pub async fn delete(req: HttpRequest, path: Path<i32>, db: Data<Addr<DbExecutor>>) -> HttpResponse {
|
||||||
HttpResponse::Ok().content_type("text/html").body("")
|
let user = match user_from_request(req, &db).await {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(response) => return response,
|
||||||
|
};
|
||||||
|
let comment_id = path.into_inner();
|
||||||
|
let msg = DeleteComment {
|
||||||
|
user_id: user.id,
|
||||||
|
comment_id,
|
||||||
|
};
|
||||||
|
match db.send(msg).await {
|
||||||
|
Ok(Ok(_)) => (),
|
||||||
|
Ok(Err(_)) => (),
|
||||||
|
_ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(),
|
||||||
|
};
|
||||||
|
HttpResponse::NoContent().body("")
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use actix::Addr;
|
||||||
|
use actix_web::web::{Data, Json, Path};
|
||||||
|
use actix_web::{delete, get, post, put, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
|
use jirs_data::ResponseData;
|
||||||
|
|
||||||
use crate::db::authorize_user::AuthorizeUser;
|
use crate::db::authorize_user::AuthorizeUser;
|
||||||
use crate::db::comments::LoadIssueComments;
|
use crate::db::comments::LoadIssueComments;
|
||||||
use crate::db::issues::{LoadIssue, UpdateIssue};
|
use crate::db::issues::{CreateIssue, DeleteIssue, LoadIssue, UpdateIssue};
|
||||||
use crate::db::users::{LoadIssueAssignees, LoadProjectUsers};
|
use crate::db::users::{LoadIssueAssignees, LoadProjectUsers};
|
||||||
use crate::db::DbExecutor;
|
use crate::db::DbExecutor;
|
||||||
use crate::errors::ServiceErrors;
|
use crate::errors::ServiceErrors;
|
||||||
use crate::middleware::authorize::token_from_headers;
|
use crate::middleware::authorize::token_from_headers;
|
||||||
use actix::Addr;
|
use crate::routes::user_from_request;
|
||||||
use actix_web::web::{Data, Json, Path};
|
|
||||||
use actix_web::{delete, get, post, put, HttpRequest, HttpResponse};
|
|
||||||
use jirs_data::ResponseData;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
#[get("")]
|
#[get("")]
|
||||||
pub async fn project_issues() -> HttpResponse {
|
pub async fn project_issues() -> HttpResponse {
|
||||||
@ -45,22 +49,35 @@ pub async fn issue_with_users_and_comments(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/")]
|
#[post("")]
|
||||||
pub async fn create(req: HttpRequest, db: Data<Addr<DbExecutor>>) -> HttpResponse {
|
pub async fn create(
|
||||||
let token = match token_from_headers(req.headers()) {
|
req: HttpRequest,
|
||||||
Ok(uuid) => uuid,
|
payload: Json<jirs_data::CreateIssuePayload>,
|
||||||
_ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(),
|
db: Data<Addr<DbExecutor>>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let user = match user_from_request(req, &db).await {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(response) => return response,
|
||||||
};
|
};
|
||||||
let _user = match db
|
let msg = CreateIssue {
|
||||||
.send(AuthorizeUser {
|
title: payload.title.clone(),
|
||||||
access_token: token,
|
issue_type: payload.issue_type.clone(),
|
||||||
})
|
status: payload.status.clone(),
|
||||||
.await
|
priority: payload.priority.clone(),
|
||||||
{
|
description: payload.description.clone(),
|
||||||
Ok(Ok(user)) => user,
|
description_text: payload.description_text.clone(),
|
||||||
_ => return crate::errors::ServiceErrors::Unauthorized.into_http_response(),
|
estimate: payload.estimate.clone(),
|
||||||
|
time_spent: payload.time_spent.clone(),
|
||||||
|
time_remaining: payload.time_remaining.clone(),
|
||||||
|
project_id: payload.project_id,
|
||||||
|
reporter_id: user.id,
|
||||||
|
user_ids: payload.user_ids.clone(),
|
||||||
};
|
};
|
||||||
HttpResponse::Ok().content_type("text/html").body("")
|
match db.send(msg).await {
|
||||||
|
Ok(Ok(issue)) => HttpResponse::Ok().json(issue),
|
||||||
|
Ok(Err(e)) => e.into_http_response(),
|
||||||
|
_ => ServiceErrors::DatabaseConnectionLost.into_http_response(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("/{id}")]
|
#[put("/{id}")]
|
||||||
@ -112,8 +129,18 @@ pub async fn update(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[delete("/{id}")]
|
#[delete("/{id}")]
|
||||||
pub async fn delete() -> HttpResponse {
|
pub async fn delete(req: HttpRequest, path: Path<i32>, db: Data<Addr<DbExecutor>>) -> HttpResponse {
|
||||||
HttpResponse::Ok().content_type("text/html").body("")
|
let _user = match user_from_request(req, &db).await {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(response) => return response,
|
||||||
|
};
|
||||||
|
let issue_id = path.into_inner();
|
||||||
|
let msg = DeleteIssue { issue_id };
|
||||||
|
match db.send(msg).await {
|
||||||
|
Ok(Ok(_)) => HttpResponse::NoContent().body(""),
|
||||||
|
Ok(Err(e)) => e.into_http_response(),
|
||||||
|
_ => ServiceErrors::DatabaseConnectionLost.into_http_response(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_issue(
|
async fn load_issue(
|
||||||
|
@ -1,4 +1,34 @@
|
|||||||
|
use actix::Addr;
|
||||||
|
use actix_web::web::Data;
|
||||||
|
use actix_web::{HttpRequest, HttpResponse};
|
||||||
|
|
||||||
|
use crate::db::authorize_user::AuthorizeUser;
|
||||||
|
use crate::db::DbExecutor;
|
||||||
|
use crate::errors::ServiceErrors;
|
||||||
|
use crate::middleware::authorize::token_from_headers;
|
||||||
|
use crate::models::User;
|
||||||
|
|
||||||
pub mod comments;
|
pub mod comments;
|
||||||
pub mod issues;
|
pub mod issues;
|
||||||
pub mod projects;
|
pub mod projects;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
|
||||||
|
pub async fn user_from_request(
|
||||||
|
req: HttpRequest,
|
||||||
|
db: &Data<Addr<DbExecutor>>,
|
||||||
|
) -> Result<User, HttpResponse> {
|
||||||
|
let token = match token_from_headers(req.headers()) {
|
||||||
|
Ok(uuid) => uuid,
|
||||||
|
_ => return Err(ServiceErrors::Unauthorized.into_http_response()),
|
||||||
|
};
|
||||||
|
match db
|
||||||
|
.send(AuthorizeUser {
|
||||||
|
access_token: token,
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(Ok(user)) => Ok(user),
|
||||||
|
Ok(Err(e)) => Err(e.into_http_response()),
|
||||||
|
_ => Err(ServiceErrors::Unauthorized.into_http_response()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
|
use actix::Addr;
|
||||||
|
use actix_web::web::{Data, Json, Path};
|
||||||
|
use actix_web::{get, put, HttpRequest, HttpResponse};
|
||||||
|
|
||||||
|
use jirs_data::ResponseData;
|
||||||
|
|
||||||
use crate::db::authorize_user::AuthorizeUser;
|
use crate::db::authorize_user::AuthorizeUser;
|
||||||
use crate::db::issues::LoadProjectIssues;
|
use crate::db::issues::LoadProjectIssues;
|
||||||
use crate::db::projects::LoadCurrentProject;
|
use crate::db::projects::{LoadCurrentProject, UpdateProject};
|
||||||
use crate::db::users::LoadProjectUsers;
|
use crate::db::users::LoadProjectUsers;
|
||||||
use crate::db::DbExecutor;
|
use crate::db::DbExecutor;
|
||||||
use crate::errors::ServiceErrors;
|
use crate::errors::ServiceErrors;
|
||||||
use crate::middleware::authorize::token_from_headers;
|
use crate::middleware::authorize::token_from_headers;
|
||||||
use actix::Addr;
|
use crate::routes::user_from_request;
|
||||||
use actix_web::web::Data;
|
|
||||||
use actix_web::{get, put, HttpRequest, HttpResponse};
|
|
||||||
use jirs_data::ResponseData;
|
|
||||||
|
|
||||||
#[get("")]
|
#[get("")]
|
||||||
pub async fn project_with_users_and_issues(
|
pub async fn project_with_users_and_issues(
|
||||||
@ -73,7 +76,27 @@ pub async fn project_with_users_and_issues(
|
|||||||
HttpResponse::Ok().json(res.into_response())
|
HttpResponse::Ok().json(res.into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[put("")]
|
#[put("/{id}")]
|
||||||
pub async fn update(_req: HttpRequest, _db: Data<Addr<DbExecutor>>) -> HttpResponse {
|
pub async fn update(
|
||||||
HttpResponse::NotImplemented().body("")
|
req: HttpRequest,
|
||||||
|
payload: Json<jirs_data::UpdateProjectPayload>,
|
||||||
|
path: Path<i32>,
|
||||||
|
db: Data<Addr<DbExecutor>>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let _user = match user_from_request(req, &db).await {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(response) => return response,
|
||||||
|
};
|
||||||
|
let msg = UpdateProject {
|
||||||
|
project_id: path.into_inner(),
|
||||||
|
name: payload.name.clone(),
|
||||||
|
url: payload.url.clone(),
|
||||||
|
description: payload.description.clone(),
|
||||||
|
category: payload.category.clone(),
|
||||||
|
};
|
||||||
|
match db.send(msg).await {
|
||||||
|
Ok(Ok(project)) => HttpResponse::Ok().json(project),
|
||||||
|
Ok(Err(e)) => e.into_http_response(),
|
||||||
|
_ => ServiceErrors::DatabaseConnectionLost.into_http_response(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
{
|
[
|
||||||
|
{
|
||||||
|
"test": ".*.tsx?$",
|
||||||
"jsc": {
|
"jsc": {
|
||||||
"parser": {
|
"parser": {
|
||||||
"syntax": "typescript",
|
"syntax": "typescript",
|
||||||
@ -7,5 +9,29 @@
|
|||||||
"dynamicImport": true
|
"dynamicImport": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"test": ".jsx?$",
|
||||||
|
"jsc": {
|
||||||
|
"target": "es2018",
|
||||||
|
"parser": {
|
||||||
|
"syntax": "ecmascript",
|
||||||
|
"jsx": true,
|
||||||
|
"dynamicImport": true,
|
||||||
|
"numericSeparator": true,
|
||||||
|
"classPrivateProperty": true,
|
||||||
|
"privateMethod": true,
|
||||||
|
"classProperty": true,
|
||||||
|
"functionBind": true,
|
||||||
|
"exportDefaultFrom": true,
|
||||||
|
"exportNamespaceFrom": true,
|
||||||
|
"decorators": true,
|
||||||
|
"decoratorsBeforeExport": true,
|
||||||
|
"nullishCoalescing": true,
|
||||||
|
"topLevelAwait": true,
|
||||||
|
"importMeta": true,
|
||||||
|
"optionalChaining": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
@ -47,7 +47,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@4tw/cypress-drag-drop": "^1.3.0",
|
"@4tw/cypress-drag-drop": "^1.3.0",
|
||||||
"axios": "^0.19.0",
|
"@types/axios": "^0.14.0",
|
||||||
|
"@types/react-redux": "^7.1.7",
|
||||||
|
"@types/redux": "^3.6.0",
|
||||||
|
"@types/redux-actions": "^2.6.1",
|
||||||
|
"@types/redux-saga": "^0.10.5",
|
||||||
|
"axios": "^0.19.2",
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.2",
|
||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"core-js": "^3.4.7",
|
"core-js": "^3.4.7",
|
||||||
@ -65,9 +70,13 @@
|
|||||||
"react-beautiful-dnd": "^12.2.0",
|
"react-beautiful-dnd": "^12.2.0",
|
||||||
"react-content-loader": "^4.3.3",
|
"react-content-loader": "^4.3.3",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
|
"react-redux": "^7.2.0",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.1.2",
|
||||||
"react-textarea-autosize": "^7.1.2",
|
"react-textarea-autosize": "^7.1.2",
|
||||||
"react-transition-group": "^4.3.0",
|
"react-transition-group": "^4.3.0",
|
||||||
|
"redux": "^4.0.5",
|
||||||
|
"redux-actions": "^2.6.5",
|
||||||
|
"redux-saga": "^1.1.3",
|
||||||
"regenerator-runtime": "^0.13.3",
|
"regenerator-runtime": "^0.13.3",
|
||||||
"styled-components": "^4.4.1",
|
"styled-components": "^4.4.1",
|
||||||
"sweet-pubsub": "^1.1.2"
|
"sweet-pubsub": "^1.1.2"
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React from 'react';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
|
import store from '../store';
|
||||||
|
|
||||||
import NormalizeStyles from './NormalizeStyles';
|
import NormalizeStyles from './NormalizeStyles';
|
||||||
import BaseStyles from './BaseStyles';
|
import BaseStyles from './BaseStyles';
|
||||||
import Toast from './Toast';
|
import Toast from './Toast';
|
||||||
import Routes from './Routes';
|
import Routes from './Routes';
|
||||||
|
|
||||||
// We're importing .css because @font-face in styled-components causes font files
|
|
||||||
// to be constantly re-requested from the server (which causes screen flicker)
|
|
||||||
// https://github.com/styled-components/styled-components/issues/1593
|
|
||||||
import './fontStyles.css';
|
import './fontStyles.css';
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<Fragment>
|
<Provider store={ store }>
|
||||||
<NormalizeStyles />
|
<NormalizeStyles/>
|
||||||
<BaseStyles />
|
<BaseStyles/>
|
||||||
<Toast />
|
<Toast/>
|
||||||
<Routes />
|
<Routes/>
|
||||||
</Fragment>
|
</Provider>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,31 +1,56 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { connect } from "react-redux";
|
||||||
|
import { Redirect } from 'react-router-dom';
|
||||||
|
|
||||||
import api from 'shared/utils/api';
|
import * as formActions from 'actions/forms';
|
||||||
import toast from 'shared/utils/toast';
|
import { getStoredAuthToken } from 'shared/utils/authToken';
|
||||||
import { getStoredAuthToken, storeAuthToken } from 'shared/utils/authToken';
|
|
||||||
import { PageLoader } from 'shared/components';
|
|
||||||
|
|
||||||
const Authenticate = () => {
|
import {
|
||||||
const history = useHistory();
|
ActionButton,
|
||||||
|
Actions,
|
||||||
|
Divider,
|
||||||
|
FormElement,
|
||||||
|
Header,
|
||||||
|
SignIn,
|
||||||
|
SignInSection,
|
||||||
|
} from 'Project/IssueCreate/Styles';
|
||||||
|
|
||||||
useEffect(() => {
|
const Authenticate = ({
|
||||||
const createGuestAccount = async () => {
|
onEmailChanged,
|
||||||
try {
|
onPasswordChanged,
|
||||||
const { authToken } = await api.post('/authentication/guest');
|
onSubmit,
|
||||||
storeAuthToken(authToken);
|
}) => {
|
||||||
history.push('/');
|
if (getStoredAuthToken()) return <Redirect to='/project'/>;
|
||||||
} catch (error) {
|
|
||||||
toast.error(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!getStoredAuthToken()) {
|
return (
|
||||||
createGuestAccount();
|
<SignIn>
|
||||||
}
|
<SignInSection>
|
||||||
}, [history]);
|
<form onSubmit={ onSubmit }>
|
||||||
|
<FormElement>
|
||||||
return <PageLoader />;
|
<Header>Zaloguj się na swoje konto</Header>
|
||||||
|
<input
|
||||||
|
name='email'
|
||||||
|
onChange={ onEmailChanged }
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
name='password'
|
||||||
|
onChange={ onPasswordChanged }
|
||||||
|
/>
|
||||||
|
<Divider/>
|
||||||
|
<Actions>
|
||||||
|
<ActionButton type="submit" variant="primary" isWorking={ false }>
|
||||||
|
Zaloguj
|
||||||
|
</ActionButton>
|
||||||
|
</Actions>
|
||||||
|
</FormElement>
|
||||||
|
</form>
|
||||||
|
</SignInSection>
|
||||||
|
</SignIn>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Authenticate;
|
export default connect(null, {
|
||||||
|
onEmailChanged: formActions.emailChanged,
|
||||||
|
onPasswordChanged: formActions.passwordChanged,
|
||||||
|
onSubmit: formActions.signInSubmit,
|
||||||
|
})(Authenticate);
|
||||||
|
39
react-client/src/Auth/Styles.js
vendored
Normal file
39
react-client/src/Auth/Styles.js
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import styled from 'styled-components/dist/styled-components.esm';
|
||||||
|
|
||||||
|
import { color, font } from 'shared/utils/styles';
|
||||||
|
import { Button, Form } from 'shared/components';
|
||||||
|
|
||||||
|
export const FormElement = styled(Form.Element)`;
|
||||||
|
padding: 25px 40px 35px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FormHeading = styled.div`
|
||||||
|
padding-bottom: 15px;
|
||||||
|
${ font.size(21) }
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SelectItem = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 15px;
|
||||||
|
${ props => props.withBottomMargin && `margin-bottom: 5px;` }
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SelectItemLabel = styled.div`
|
||||||
|
padding: 0 3px 0 6px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Divider = styled.div`
|
||||||
|
margin-top: 22px;
|
||||||
|
border-top: 1px solid ${ color.borderLightest };
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Actions = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding-top: 30px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const ActionButton = styled(Button)`
|
||||||
|
margin-left: 10px;
|
||||||
|
`;
|
@ -1,4 +1,4 @@
|
|||||||
import React, { Fragment, useRef } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { Textarea } from 'shared/components';
|
import { Textarea } from 'shared/components';
|
||||||
@ -13,41 +13,45 @@ const propTypes = {
|
|||||||
onCancel: PropTypes.func.isRequired,
|
onCancel: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProjectBoardIssueDetailsCommentsBodyForm = ({
|
class ProjectBoardIssueDetailsCommentsBodyForm extends React.Component {
|
||||||
|
state = { textArea: React.createRef() };
|
||||||
|
|
||||||
|
|
||||||
|
handleSubmit = () => {
|
||||||
|
if (this.state.textArea.current.value.trim()) {
|
||||||
|
this.props.onSubmit();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let {
|
||||||
value,
|
value,
|
||||||
onChange,
|
onChange,
|
||||||
isWorking,
|
isWorking,
|
||||||
onSubmit,
|
|
||||||
onCancel,
|
onCancel,
|
||||||
}) => {
|
} = this.props;
|
||||||
const $textareaRef = useRef();
|
|
||||||
|
|
||||||
const handleSubmit = () => {
|
|
||||||
if ($textareaRef.current.value.trim()) {
|
|
||||||
onSubmit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Textarea
|
<Textarea
|
||||||
autoFocus
|
autoFocus
|
||||||
placeholder="Add a comment..."
|
placeholder="Add a comment..."
|
||||||
value={value}
|
value={ value }
|
||||||
onChange={onChange}
|
onChange={ onChange }
|
||||||
ref={$textareaRef}
|
ref={ this.state.textArea }
|
||||||
/>
|
/>
|
||||||
<Actions>
|
<Actions>
|
||||||
<FormButton variant="primary" isWorking={isWorking} onClick={handleSubmit}>
|
<FormButton variant="primary" isWorking={ isWorking } onClick={ this.handleSubmit }>
|
||||||
Save
|
Save
|
||||||
</FormButton>
|
</FormButton>
|
||||||
<FormButton variant="empty" onClick={onCancel}>
|
<FormButton variant="empty" onClick={ onCancel }>
|
||||||
Cancel
|
Cancel
|
||||||
</FormButton>
|
</FormButton>
|
||||||
</Actions>
|
</Actions>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ProjectBoardIssueDetailsCommentsBodyForm.propTypes = propTypes;
|
ProjectBoardIssueDetailsCommentsBodyForm.propTypes = propTypes;
|
||||||
|
|
||||||
|
@ -1,62 +1,74 @@
|
|||||||
import React, { Fragment, useState } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import api from 'shared/utils/api';
|
import api from 'shared/utils/api';
|
||||||
import useCurrentUser from 'shared/hooks/currentUser';
|
|
||||||
import toast from 'shared/utils/toast';
|
import toast from 'shared/utils/toast';
|
||||||
|
import { fetchCurrentUser } from "actions/users";
|
||||||
|
|
||||||
import BodyForm from '../BodyForm';
|
import BodyForm from '../BodyForm';
|
||||||
import ProTip from './ProTip';
|
import ProTip from './ProTip';
|
||||||
import { Create, UserAvatar, Right, FakeTextarea } from './Styles';
|
import { Create, FakeTextarea, Right, UserAvatar } from './Styles';
|
||||||
|
|
||||||
const propTypes = {
|
class ProjectBoardIssueDetailsCommentsCreate extends React.Component {
|
||||||
issueId: PropTypes.number.isRequired,
|
state = { isFormOpen: false, isCreating: false, body: '', currentUser: null };
|
||||||
fetchIssue: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProjectBoardIssueDetailsCommentsCreate = ({ issueId, fetchIssue }) => {
|
setFormClosed = () => this.setState({ isFormOpen: false });
|
||||||
const [isFormOpen, setFormOpen] = useState(false);
|
setFormOpened = () => this.setState({ isFormOpen: true });
|
||||||
const [isCreating, setCreating] = useState(false);
|
setBody = body => this.setState({ body });
|
||||||
const [body, setBody] = useState('');
|
setFormOpen = isFormOpen => this.setState({ isFormOpen });
|
||||||
|
setCreatingTrue = () => this.setState({ isCreating: true });
|
||||||
|
|
||||||
const { currentUser } = useCurrentUser();
|
componentDidMount() {
|
||||||
|
this.props.fetchCurrentUser({});
|
||||||
|
}
|
||||||
|
|
||||||
const handleCommentCreate = async () => {
|
handleCommentCreate = async () => {
|
||||||
try {
|
try {
|
||||||
setCreating(true);
|
this.setCreatingTrue();
|
||||||
await api.post(`/comments`, { body, issueId, userId: currentUser.id });
|
const response = await api.post(`/comments`, { body: this.state.body, issueId: this.props.issueId });
|
||||||
await fetchIssue();
|
console.log(response);
|
||||||
setFormOpen(false);
|
await this.props.fetchIssue();
|
||||||
setCreating(false);
|
this.setState({ isCreating: false, isFormOpen: false, body: '' });
|
||||||
setBody('');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
|
this.setState({ isCreating: false });
|
||||||
toast.error(error);
|
toast.error(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { body, isFormOpen, isCreating } = this.state;
|
||||||
|
const { currentUser } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Create>
|
<Create>
|
||||||
{currentUser && <UserAvatar name={currentUser.name} avatarUrl={currentUser.avatarUrl} />}
|
{ currentUser && <UserAvatar name={ currentUser.name } avatarUrl={ currentUser.avatarUrl }/> }
|
||||||
<Right>
|
<Right>
|
||||||
{isFormOpen ? (
|
{ isFormOpen ? (
|
||||||
<BodyForm
|
<BodyForm
|
||||||
value={body}
|
value={ body }
|
||||||
onChange={setBody}
|
onChange={ this.setBody }
|
||||||
isWorking={isCreating}
|
isWorking={ isCreating }
|
||||||
onSubmit={handleCommentCreate}
|
onSubmit={ this.handleCommentCreate }
|
||||||
onCancel={() => setFormOpen(false)}
|
onCancel={ this.setFormClosed }
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<FakeTextarea onClick={() => setFormOpen(true)}>Add a comment...</FakeTextarea>
|
<FakeTextarea onClick={ this.setFormOpened }>Add a comment...</FakeTextarea>
|
||||||
<ProTip setFormOpen={setFormOpen} />
|
<ProTip setFormOpen={ this.setFormOpen }/>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
) }
|
||||||
</Right>
|
</Right>
|
||||||
</Create>
|
</Create>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProjectBoardIssueDetailsCommentsCreate.propTypes = {
|
||||||
|
issueId: PropTypes.number.isRequired,
|
||||||
|
fetchIssue: PropTypes.func.isRequired,
|
||||||
|
fetchCurrentUser: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
ProjectBoardIssueDetailsCommentsCreate.propTypes = propTypes;
|
export default connect(({ users: { currentUser } }) => ({ currentUser }), { fetchCurrentUser })(ProjectBoardIssueDetailsCommentsCreate);
|
||||||
|
|
||||||
export default ProjectBoardIssueDetailsCommentsCreate;
|
|
||||||
|
27
react-client/src/Project/IssueCreate/Styles.js
vendored
27
react-client/src/Project/IssueCreate/Styles.js
vendored
@ -1,15 +1,36 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
import { color, font } from 'shared/utils/styles';
|
import { color, font } from 'shared/utils/styles';
|
||||||
import { Button, Form } from 'shared/components';
|
import { Button } from 'shared/components';
|
||||||
|
|
||||||
export const FormElement = styled(Form.Element)`
|
export const SignIn = styled.article`
|
||||||
|
margin: 24px auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SignInSection = styled.section`
|
||||||
|
padding: 32px 40px;
|
||||||
|
width: 400px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.1) 0px 0px 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Header = styled.h5`
|
||||||
|
color: rgb(94, 108, 132);
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: -0.048px;
|
||||||
|
line-height: 18.2833px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const FormElement = styled.div`
|
||||||
padding: 25px 40px 35px;
|
padding: 25px 40px 35px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const FormHeading = styled.div`
|
export const FormHeading = styled.div`
|
||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
${font.size(21)}
|
${ font.size(21) }
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SelectItem = styled.div`
|
export const SelectItem = styled.div`
|
||||||
|
@ -1,130 +1,129 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import {
|
import { IssuePriority, IssuePriorityCopy, IssueStatus, IssueType, IssueTypeCopy, } from 'shared/constants/issues';
|
||||||
IssueType,
|
|
||||||
IssueStatus,
|
|
||||||
IssuePriority,
|
|
||||||
IssueTypeCopy,
|
|
||||||
IssuePriorityCopy,
|
|
||||||
} from 'shared/constants/issues';
|
|
||||||
import toast from 'shared/utils/toast';
|
import toast from 'shared/utils/toast';
|
||||||
import useApi from 'shared/hooks/api';
|
import api from 'shared/utils/api';
|
||||||
import useCurrentUser from 'shared/hooks/currentUser';
|
import { Avatar, Form, Icon, IssuePriorityIcon, IssueTypeIcon } from 'shared/components';
|
||||||
import { Form, IssueTypeIcon, Icon, Avatar, IssuePriorityIcon } from 'shared/components';
|
|
||||||
|
|
||||||
import {
|
import { ActionButton, Actions, Divider, FormElement, FormHeading, SelectItem, SelectItemLabel, } from './Styles';
|
||||||
FormHeading,
|
|
||||||
FormElement,
|
|
||||||
SelectItem,
|
|
||||||
SelectItemLabel,
|
|
||||||
Divider,
|
|
||||||
Actions,
|
|
||||||
ActionButton,
|
|
||||||
} from './Styles';
|
|
||||||
|
|
||||||
const propTypes = {
|
class ProjectIssueCreate extends React.Component {
|
||||||
project: PropTypes.object.isRequired,
|
state = {
|
||||||
fetchProject: PropTypes.func.isRequired,
|
isCreating: false, form: {
|
||||||
onCreate: PropTypes.func.isRequired,
|
|
||||||
modalClose: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProjectIssueCreate = ({ project, fetchProject, onCreate, modalClose }) => {
|
|
||||||
const [{ isCreating }, createIssue] = useApi.post('/issues');
|
|
||||||
|
|
||||||
const { currentUserId } = useCurrentUser();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
enableReinitialize
|
|
||||||
initialValues={{
|
|
||||||
type: IssueType.TASK,
|
type: IssueType.TASK,
|
||||||
title: '',
|
title: '',
|
||||||
description: '',
|
description: '',
|
||||||
reporterId: currentUserId,
|
reporterId: null,
|
||||||
userIds: [],
|
userIds: [],
|
||||||
priority: IssuePriority.MEDIUM,
|
priority: IssuePriority.MEDIUM,
|
||||||
}}
|
}
|
||||||
validations={{
|
};
|
||||||
type: Form.is.required(),
|
|
||||||
title: [Form.is.required(), Form.is.maxLength(200)],
|
onSubmit = async () => {
|
||||||
reporterId: Form.is.required(),
|
let { project, fetchProject, onCreate } = this.props;
|
||||||
priority: Form.is.required(),
|
|
||||||
}}
|
this.setState({ isCreating: true });
|
||||||
onSubmit={async (values, form) => {
|
|
||||||
try {
|
try {
|
||||||
await createIssue({
|
await api.post('/issues', {
|
||||||
...values,
|
...this.state.form,
|
||||||
status: IssueStatus.BACKLOG,
|
status: IssueStatus.BACKLOG,
|
||||||
projectId: project.id,
|
projectId: project.id,
|
||||||
users: values.userIds.map(id => ({ id })),
|
// userIds: values.userIds,
|
||||||
});
|
});
|
||||||
await fetchProject();
|
await fetchProject();
|
||||||
toast.success('Issue has been successfully created.');
|
toast.success('Issue has been successfully created.');
|
||||||
onCreate();
|
onCreate();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Form.handleAPIError(error, form);
|
|
||||||
}
|
}
|
||||||
}}
|
this.setState({ isCreating: false });
|
||||||
|
};
|
||||||
|
|
||||||
|
onInputChange = (field, value) => {
|
||||||
|
this.setState({
|
||||||
|
form: {
|
||||||
|
...this.state.form,
|
||||||
|
[field]: value,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let { project, modalClose } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form
|
||||||
|
enableReinitialize
|
||||||
|
initialValues={ this.state.form }
|
||||||
|
validations={ {} }
|
||||||
|
validate={ () => true }
|
||||||
|
onSubmit={ this.onSubmit }
|
||||||
>
|
>
|
||||||
<FormElement>
|
<FormElement>
|
||||||
<FormHeading>Create issue</FormHeading>
|
<FormHeading>
|
||||||
|
Create issue
|
||||||
|
</FormHeading>
|
||||||
<Form.Field.Select
|
<Form.Field.Select
|
||||||
name="type"
|
name="type"
|
||||||
label="Issue Type"
|
label="Issue Type"
|
||||||
tip="Start typing to get a list of possible matches."
|
tip="Start typing to get a list of possible matches."
|
||||||
options={typeOptions}
|
options={ typeOptions }
|
||||||
renderOption={renderType}
|
renderOption={ renderType }
|
||||||
renderValue={renderType}
|
renderValue={ renderType }
|
||||||
/>
|
/>
|
||||||
<Divider />
|
<Divider/>
|
||||||
<Form.Field.Input
|
<Form.Field.Input
|
||||||
name="title"
|
name="title"
|
||||||
label="Short Summary"
|
label="Short Summary"
|
||||||
tip="Concisely summarize the issue in one or two sentences."
|
tip="Concisely summarize the issue in one or two sentences."
|
||||||
|
onChange={ this.onInputChange }
|
||||||
/>
|
/>
|
||||||
<Form.Field.TextEditor
|
<Form.Field.TextEditor
|
||||||
name="description"
|
name="description"
|
||||||
label="Description"
|
label="Description"
|
||||||
tip="Describe the issue in as much detail as you'd like."
|
tip="Describe the issue in as much detail as you'd like."
|
||||||
|
onChange={ this.onInputChange }
|
||||||
/>
|
/>
|
||||||
<Form.Field.Select
|
<Form.Field.Select
|
||||||
name="reporterId"
|
name="reporterId"
|
||||||
label="Reporter"
|
label="Reporter"
|
||||||
options={userOptions(project)}
|
options={ userOptions(project) }
|
||||||
renderOption={renderUser(project)}
|
renderOption={ renderUser(project) }
|
||||||
renderValue={renderUser(project)}
|
renderValue={ renderUser(project) }
|
||||||
|
onChange={ this.onInputChange }
|
||||||
/>
|
/>
|
||||||
<Form.Field.Select
|
<Form.Field.Select
|
||||||
isMulti
|
isMulti
|
||||||
name="userIds"
|
name="userIds"
|
||||||
label="Assignees"
|
label="Assignees"
|
||||||
tio="People who are responsible for dealing with this issue."
|
tio="People who are responsible for dealing with this issue."
|
||||||
options={userOptions(project)}
|
onChange={ this.onInputChange }
|
||||||
renderOption={renderUser(project)}
|
options={ userOptions(project) }
|
||||||
renderValue={renderUser(project)}
|
renderOption={ renderUser(project) }
|
||||||
|
renderValue={ renderUser(project) }
|
||||||
/>
|
/>
|
||||||
<Form.Field.Select
|
<Form.Field.Select
|
||||||
name="priority"
|
name="priority"
|
||||||
label="Priority"
|
label="Priority"
|
||||||
tip="Priority in relation to other issues."
|
tip="Priority in relation to other issues."
|
||||||
options={priorityOptions}
|
options={ priorityOptions }
|
||||||
renderOption={renderPriority}
|
renderOption={ renderPriority }
|
||||||
renderValue={renderPriority}
|
renderValue={ renderPriority }
|
||||||
|
onChange={ this.onInputChange }
|
||||||
/>
|
/>
|
||||||
<Actions>
|
<Actions>
|
||||||
<ActionButton type="submit" variant="primary" isWorking={isCreating}>
|
<ActionButton type="submit" variant="primary" onClick={ this.onSubmit }>
|
||||||
Create Issue
|
Create Issue
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton type="button" variant="empty" onClick={modalClose}>
|
<ActionButton type="button" variant="empty" onClick={ modalClose }>
|
||||||
Cancel
|
Cancel
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Actions>
|
</Actions>
|
||||||
</FormElement>
|
</FormElement>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const typeOptions = Object.values(IssueType).map(type => ({
|
const typeOptions = Object.values(IssueType).map(type => ({
|
||||||
value: type,
|
value: type,
|
||||||
@ -140,15 +139,15 @@ const userOptions = project => project.users.map(user => ({ value: user.id, labe
|
|||||||
|
|
||||||
const renderType = ({ value: type }) => (
|
const renderType = ({ value: type }) => (
|
||||||
<SelectItem>
|
<SelectItem>
|
||||||
<IssueTypeIcon type={type} top={1} />
|
<IssueTypeIcon type={ type } top={ 1 }/>
|
||||||
<SelectItemLabel>{IssueTypeCopy[type]}</SelectItemLabel>
|
<SelectItemLabel>{ IssueTypeCopy[type] }</SelectItemLabel>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderPriority = ({ value: priority }) => (
|
const renderPriority = ({ value: priority }) => (
|
||||||
<SelectItem>
|
<SelectItem>
|
||||||
<IssuePriorityIcon priority={priority} top={1} />
|
<IssuePriorityIcon priority={ priority } top={ 1 }/>
|
||||||
<SelectItemLabel>{IssuePriorityCopy[priority]}</SelectItemLabel>
|
<SelectItemLabel>{ IssuePriorityCopy[priority] }</SelectItemLabel>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -157,17 +156,22 @@ const renderUser = project => ({ value: userId, removeOptionValue }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={user.id}
|
key={ user.id }
|
||||||
withBottomMargin={!!removeOptionValue}
|
withBottomMargin={ !!removeOptionValue }
|
||||||
onClick={() => removeOptionValue && removeOptionValue()}
|
onClick={ () => removeOptionValue && removeOptionValue() }
|
||||||
>
|
>
|
||||||
<Avatar size={20} avatarUrl={user.avatarUrl} name={user.name} />
|
<Avatar size={ 20 } avatarUrl={ user.avatarUrl } name={ user.name }/>
|
||||||
<SelectItemLabel>{user.name}</SelectItemLabel>
|
<SelectItemLabel>{ user.name }</SelectItemLabel>
|
||||||
{removeOptionValue && <Icon type="close" top={2} />}
|
{ removeOptionValue && <Icon type="close" top={ 2 }/> }
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
ProjectIssueCreate.propTypes = propTypes;
|
ProjectIssueCreate.propTypes = {
|
||||||
|
project: PropTypes.object.isRequired,
|
||||||
|
fetchProject: PropTypes.func.isRequired,
|
||||||
|
onCreate: PropTypes.func.isRequired,
|
||||||
|
modalClose: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default ProjectIssueCreate;
|
export default ProjectIssueCreate;
|
||||||
|
@ -4,17 +4,12 @@ import PropTypes from 'prop-types';
|
|||||||
import { ProjectCategory, ProjectCategoryCopy } from 'shared/constants/projects';
|
import { ProjectCategory, ProjectCategoryCopy } from 'shared/constants/projects';
|
||||||
import toast from 'shared/utils/toast';
|
import toast from 'shared/utils/toast';
|
||||||
import useApi from 'shared/hooks/api';
|
import useApi from 'shared/hooks/api';
|
||||||
import { Form, Breadcrumbs } from 'shared/components';
|
import { Breadcrumbs, Form } from 'shared/components';
|
||||||
|
|
||||||
import { FormCont, FormHeading, FormElement, ActionButton } from './Styles';
|
import { ActionButton, FormCont, FormElement, FormHeading } from './Styles';
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
project: PropTypes.object.isRequired,
|
|
||||||
fetchProject: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ProjectSettings = ({ project, fetchProject }) => {
|
const ProjectSettings = ({ project, fetchProject }) => {
|
||||||
const [{ isUpdating }, updateProject] = useApi.put('/project');
|
const [ { isUpdating }, updateProject ] = useApi.put(`/project/${ project.id }`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
@ -67,6 +62,9 @@ const categoryOptions = Object.values(ProjectCategory).map(category => ({
|
|||||||
label: ProjectCategoryCopy[category],
|
label: ProjectCategoryCopy[category],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
ProjectSettings.propTypes = propTypes;
|
ProjectSettings.propTypes = {
|
||||||
|
project: PropTypes.object.isRequired,
|
||||||
|
fetchProject: PropTypes.func.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
export default ProjectSettings;
|
export default ProjectSettings;
|
||||||
|
9
react-client/src/actions/forms.ts
Normal file
9
react-client/src/actions/forms.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { createAction } from "redux-actions";
|
||||||
|
|
||||||
|
import { ActionType } from 'reducers/types';
|
||||||
|
|
||||||
|
export const emailChanged = createAction(ActionType.SignInEmailChanged, event => event.target.value);
|
||||||
|
export const passwordChanged = createAction(ActionType.SignInPasswordChanged, event => event.target.value);
|
||||||
|
export const signInSubmit = createAction(ActionType.SignInRequest, event => {
|
||||||
|
event.preventDefault();
|
||||||
|
});
|
5
react-client/src/actions/users.ts
Normal file
5
react-client/src/actions/users.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { createAction } from "redux-actions";
|
||||||
|
|
||||||
|
import { ActionType, JirsAction } from "../reducers/types";
|
||||||
|
|
||||||
|
export const fetchCurrentUser = createAction<JirsAction>(ActionType.FetchCurrentUser);
|
37
react-client/src/api/index.ts
Normal file
37
react-client/src/api/index.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { getStoredAuthToken } from 'shared/utils/authToken';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export interface RequestBody {
|
||||||
|
path: string,
|
||||||
|
query?: string,
|
||||||
|
body?: object,
|
||||||
|
form?: FormData,
|
||||||
|
method?: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const endpoint = (): string => `http://localhost:3000`;
|
||||||
|
|
||||||
|
export const getContentType = (method, form) =>
|
||||||
|
method === 'GET' || form instanceof FormData
|
||||||
|
? ({})
|
||||||
|
: ({ 'Content-Type': 'application/json' });
|
||||||
|
|
||||||
|
export const buildHeaders = (method, form) => ({
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
Authorization: `Bearer ${ getStoredAuthToken() }`,
|
||||||
|
...getContentType(method, form),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const client = (method, ...argv) => axios.create(...argv);
|
||||||
|
|
||||||
|
export const request = ({ path, query = '', body, form, method = 'string' }: RequestBody) =>
|
||||||
|
method === 'GET'
|
||||||
|
? client(method, `${ endpoint() }${ path }${ query }`, {
|
||||||
|
method,
|
||||||
|
headers: buildHeaders(method, form),
|
||||||
|
})
|
||||||
|
: client(method, `${ endpoint() }${ path }${ query }`, {
|
||||||
|
method,
|
||||||
|
body: body ? JSON.stringify(body) : form,
|
||||||
|
headers: buildHeaders(method, form),
|
||||||
|
});
|
4
react-client/src/api/users.ts
Normal file
4
react-client/src/api/users.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import { request } from './index';
|
||||||
|
|
||||||
|
export const currentUser = () =>
|
||||||
|
request({ path: '/currentUser' });
|
25
react-client/src/reducers/forms.ts
Normal file
25
react-client/src/reducers/forms.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
|
import { ActionType, JirsAction } from './types';
|
||||||
|
|
||||||
|
interface SignInFormState {
|
||||||
|
email: string,
|
||||||
|
password: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialSignIn = (): SignInFormState => ({ email: '', password: '' });
|
||||||
|
|
||||||
|
const signInForm = (state: SignInFormState = initialSignIn(), { type, payload }: JirsAction) => {
|
||||||
|
switch (type) {
|
||||||
|
case ActionType.SignInPasswordChanged:
|
||||||
|
return { ...state, password: payload };
|
||||||
|
case ActionType.SignInEmailChanged:
|
||||||
|
return { ...state, email: payload };
|
||||||
|
case ActionType.SignInSuccess:
|
||||||
|
return initialSignIn();
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default combineReducers({ signInForm })
|
9
react-client/src/reducers/index.ts
Normal file
9
react-client/src/reducers/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
|
import users from './users'
|
||||||
|
import forms from './forms'
|
||||||
|
|
||||||
|
export default combineReducers({
|
||||||
|
users,
|
||||||
|
forms,
|
||||||
|
});
|
25
react-client/src/reducers/types.ts
Normal file
25
react-client/src/reducers/types.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Action } from 'redux';
|
||||||
|
|
||||||
|
export interface User {
|
||||||
|
avatarUrl: string,
|
||||||
|
createdAt: string,
|
||||||
|
email: string,
|
||||||
|
id: number,
|
||||||
|
name: string,
|
||||||
|
projectId: number,
|
||||||
|
updatedAt: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ActionType {
|
||||||
|
CurrentUser = 'CurrentUser',
|
||||||
|
FetchCurrentUser = 'FetchCurrentUser',
|
||||||
|
SignInEmailChanged = 'SignInEmailChanged',
|
||||||
|
SignInPasswordChanged = 'SignInPasswordChanged',
|
||||||
|
SignInRequest = 'SignInRequest',
|
||||||
|
SignInSuccess = 'SignInSuccess',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JirsAction extends Action<ActionType> {
|
||||||
|
payload?: any,
|
||||||
|
errors?: any,
|
||||||
|
}
|
20
react-client/src/reducers/users.ts
Normal file
20
react-client/src/reducers/users.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { combineReducers } from 'redux';
|
||||||
|
|
||||||
|
import { ActionType, JirsAction, User } from './types';
|
||||||
|
|
||||||
|
interface CurrentUserState extends User {
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialCurrentUser = (): User => null;
|
||||||
|
|
||||||
|
export const currentUser = (state: CurrentUserState = initialCurrentUser(), { type, payload, }: JirsAction) => {
|
||||||
|
switch (type) {
|
||||||
|
case ActionType.CurrentUser: {
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default combineReducers({ currentUser })
|
10
react-client/src/sagas/index.ts
Normal file
10
react-client/src/sagas/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { takeEvery } from 'redux-saga/effects';
|
||||||
|
|
||||||
|
import { ActionType } from "../reducers/types";
|
||||||
|
import * as usersSaga from './users';
|
||||||
|
|
||||||
|
const main = function * () {
|
||||||
|
yield takeEvery(ActionType.FetchCurrentUser, usersSaga.fetchCurrentUser);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default main
|
9
react-client/src/sagas/users.ts
Normal file
9
react-client/src/sagas/users.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { call } from 'redux-saga/effects';
|
||||||
|
|
||||||
|
import * as usersApi from "api/users";
|
||||||
|
|
||||||
|
export const fetchCurrentUser = function * () {
|
||||||
|
// @ts-ignore
|
||||||
|
const res = yield call(usersApi.currentUser, {});
|
||||||
|
console.log('awa', res);
|
||||||
|
};
|
@ -2,13 +2,13 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { uniqueId } from 'lodash';
|
import { uniqueId } from 'lodash';
|
||||||
|
|
||||||
import Input from 'shared/components/Input';
|
import InputComponent from 'shared/components/Input';
|
||||||
import Select from 'shared/components/Select';
|
import SelectComponent from 'shared/components/Select';
|
||||||
import Textarea from 'shared/components/Textarea';
|
import TextareaComponent from 'shared/components/Textarea';
|
||||||
import TextEditor from 'shared/components/TextEditor';
|
import TextEditorComponent from 'shared/components/TextEditor';
|
||||||
import DatePicker from 'shared/components/DatePicker';
|
import DatePickerComponent from 'shared/components/DatePicker';
|
||||||
|
|
||||||
import { StyledField, FieldLabel, FieldTip, FieldError } from './Styles';
|
import { FieldError, FieldLabel, FieldTip, StyledField } from './Styles';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
@ -32,14 +32,14 @@ const generateField = FormComponent => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledField
|
<StyledField
|
||||||
className={className}
|
className={ className }
|
||||||
hasLabel={!!label}
|
hasLabel={ !!label }
|
||||||
data-testid={name ? `form-field:${name}` : 'form-field'}
|
data-testid={ name ? `form-field:${ name }` : 'form-field' }
|
||||||
>
|
>
|
||||||
{label && <FieldLabel htmlFor={fieldId}>{label}</FieldLabel>}
|
{ label && <FieldLabel htmlFor={ fieldId }>{ label }</FieldLabel> }
|
||||||
<FormComponent id={fieldId} invalid={!!error} name={name} {...otherProps} />
|
<FormComponent id={ fieldId } invalid={ !!error } name={ name } { ...otherProps } />
|
||||||
{tip && <FieldTip>{tip}</FieldTip>}
|
{ tip && <FieldTip>{ tip }</FieldTip> }
|
||||||
{error && <FieldError>{error}</FieldError>}
|
{ error && <FieldError>{ error }</FieldError> }
|
||||||
</StyledField>
|
</StyledField>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -50,10 +50,16 @@ const generateField = FormComponent => {
|
|||||||
return FieldComponent;
|
return FieldComponent;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Input = generateField(InputComponent);
|
||||||
|
export const Select = generateField(SelectComponent);
|
||||||
|
export const Textarea = generateField(TextareaComponent);
|
||||||
|
export const TextEditor = generateField(TextEditorComponent);
|
||||||
|
export const DatePicker = generateField(DatePickerComponent);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
Input: generateField(Input),
|
Input,
|
||||||
Select: generateField(Select),
|
Select,
|
||||||
Textarea: generateField(Textarea),
|
Textarea,
|
||||||
TextEditor: generateField(TextEditor),
|
TextEditor,
|
||||||
DatePicker: generateField(DatePicker),
|
DatePicker,
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Formik, Form as FormikForm, Field as FormikField } from 'formik';
|
import { Field as FormikField, Form as FormikForm, Formik } from 'formik';
|
||||||
import { get, mapValues } from 'lodash';
|
import { get, mapValues } from 'lodash';
|
||||||
|
|
||||||
import toast from 'shared/utils/toast';
|
import toast from 'shared/utils/toast';
|
||||||
import { is, generateErrors } from 'shared/utils/validation';
|
import { generateErrors, is } from 'shared/utils/validation';
|
||||||
|
|
||||||
import Field from './Field';
|
import Field from './Field';
|
||||||
|
|
||||||
@ -37,19 +37,30 @@ const Form = ({ validate, validations, ...otherProps }) => (
|
|||||||
|
|
||||||
Form.Element = props => <FormikForm noValidate {...props} />;
|
Form.Element = props => <FormikForm noValidate {...props} />;
|
||||||
|
|
||||||
Form.Field = mapValues(Field, FieldComponent => ({ name, validate, ...props }) => (
|
Form.Field = mapValues(Field, FieldComponent => ({ name, validate, ...props }) => {
|
||||||
<FormikField name={name} validate={validate}>
|
return (
|
||||||
{({ field, form: { touched, errors, setFieldValue } }) => (
|
<FormikField name={ name } validate={ validate }>
|
||||||
|
{ ({ field, form: { touched, errors, setFieldValue } }) => {
|
||||||
|
const onChange = value => {
|
||||||
|
if (props.onChange) {
|
||||||
|
props.onChange(name, value)
|
||||||
|
}
|
||||||
|
setFieldValue(name, value)
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
<FieldComponent
|
<FieldComponent
|
||||||
{...field}
|
{ ...field }
|
||||||
{...props}
|
{ ...props }
|
||||||
name={name}
|
name={ name }
|
||||||
error={get(touched, name) && get(errors, name)}
|
error={ get(touched, name) && get(errors, name) }
|
||||||
onChange={value => setFieldValue(name, value)}
|
onChange={ onChange }
|
||||||
/>
|
/>
|
||||||
)}
|
)
|
||||||
|
} }
|
||||||
</FormikField>
|
</FormikField>
|
||||||
));
|
)
|
||||||
|
});
|
||||||
|
|
||||||
Form.initialValues = (data, getFieldValues) =>
|
Form.initialValues = (data, getFieldValues) =>
|
||||||
getFieldValues((key, defaultValue = '') => {
|
getFieldValues((key, defaultValue = '') => {
|
||||||
|
5
react-client/src/shared/components/Form2/LabeledElement/Styles.js
vendored
Normal file
5
react-client/src/shared/components/Form2/LabeledElement/Styles.js
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
export const Input = styled.input``;
|
||||||
|
|
||||||
|
export const Select = styled.select``;
|
5
react-client/src/shared/components/Form2/Styles.js
vendored
Normal file
5
react-client/src/shared/components/Form2/Styles.js
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import styled from 'styled-components/dist/styled-components.esm';
|
||||||
|
|
||||||
|
export const Form = styled.form`
|
||||||
|
padding: 25px 40px 35px;
|
||||||
|
`;
|
11
react-client/src/shared/components/Form2/index.tsx
Normal file
11
react-client/src/shared/components/Form2/index.tsx
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import React from 'react';
|
||||||
|
// @ts-ignore
|
||||||
|
import { Form } from './Styles';
|
||||||
|
|
||||||
|
const FormComponent = ({ onSubmit, children }) => (
|
||||||
|
<Form onSubmit={ onSubmit }>
|
||||||
|
{ children }
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default FormComponent
|
9
react-client/src/shared/hooks/currentUser.js
vendored
9
react-client/src/shared/hooks/currentUser.js
vendored
@ -1,13 +1,12 @@
|
|||||||
import { get } from 'lodash';
|
|
||||||
|
|
||||||
import useApi from 'shared/hooks/api';
|
import useApi from 'shared/hooks/api';
|
||||||
|
|
||||||
const useCurrentUser = ({ cachePolicy = 'cache-only' } = {}) => {
|
const useCurrentUser = ({ cachePolicy = 'cache-only' } = {}) => {
|
||||||
const [{ data }] = useApi.get('/currentUser', {}, { cachePolicy });
|
const res = useApi.get('/currentUser', {}, { cachePolicy });
|
||||||
|
|
||||||
|
const [ { data } ] = res;
|
||||||
return {
|
return {
|
||||||
currentUser: get(data, 'currentUser'),
|
currentUser: data && data.currentUser,
|
||||||
currentUserId: get(data, 'currentUser.id'),
|
currentUserId: data && data.currentUser ? data.currentUser.id : null,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
29
react-client/src/shared/utils/api.js
vendored
29
react-client/src/shared/utils/api.js
vendored
@ -9,7 +9,7 @@ const defaults = {
|
|||||||
baseURL: process.env.API_URL || 'http://localhost:3000',
|
baseURL: process.env.API_URL || 'http://localhost:3000',
|
||||||
headers: () => ({
|
headers: () => ({
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: getStoredAuthToken() ? `Bearer ${getStoredAuthToken()}` : undefined,
|
Authorization: getStoredAuthToken() ? `Bearer ${ getStoredAuthToken() }` : undefined,
|
||||||
}),
|
}),
|
||||||
error: {
|
error: {
|
||||||
code: 'INTERNAL_ERROR',
|
code: 'INTERNAL_ERROR',
|
||||||
@ -19,33 +19,30 @@ const defaults = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const api = (method, url, variables) =>
|
const api = async (method, url, variables) => {
|
||||||
new Promise((resolve, reject) => {
|
try {
|
||||||
axios({
|
const response = await axios({
|
||||||
url: `${defaults.baseURL}${url}`,
|
url: `${ defaults.baseURL }${ url }`,
|
||||||
method,
|
method,
|
||||||
headers: defaults.headers(),
|
headers: defaults.headers(),
|
||||||
params: method === 'get' ? variables : undefined,
|
params: method === 'get' ? variables : undefined,
|
||||||
data: method !== 'get' ? variables : undefined,
|
data: method !== 'get' ? variables : undefined,
|
||||||
paramsSerializer: objectToQueryString,
|
paramsSerializer: objectToQueryString,
|
||||||
}).then(
|
});
|
||||||
response => {
|
return response.data;
|
||||||
resolve(response.data);
|
} catch (error) {
|
||||||
},
|
|
||||||
error => {
|
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
if (error.response.data.error.code === 'INVALID_TOKEN') {
|
if (error.response.status === 401) {
|
||||||
removeStoredAuthToken();
|
removeStoredAuthToken();
|
||||||
history.push('/authenticate');
|
history.push('/authenticate');
|
||||||
} else {
|
} else {
|
||||||
reject(error.response.data.error);
|
throw error.response.data.error;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reject(defaults.error);
|
throw defaults.error;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
);
|
};
|
||||||
});
|
|
||||||
|
|
||||||
const optimisticUpdate = async (url, { updatedFields, currentFields, setLocalData }) => {
|
const optimisticUpdate = async (url, { updatedFields, currentFields, setLocalData }) => {
|
||||||
try {
|
try {
|
||||||
|
14
react-client/src/store.ts
Normal file
14
react-client/src/store.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { applyMiddleware, compose, createStore } from 'redux';
|
||||||
|
import createSagaMiddleware from 'redux-saga';
|
||||||
|
|
||||||
|
import reducers from './reducers';
|
||||||
|
import saga from './sagas';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
|
||||||
|
|
||||||
|
const sagaMiddleware = createSagaMiddleware();
|
||||||
|
|
||||||
|
export default createStore(reducers, composeEnhancers(applyMiddleware(sagaMiddleware)));
|
||||||
|
|
||||||
|
sagaMiddleware.run(saga);
|
@ -14,9 +14,9 @@ module.exports = {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.(t|j)sx?$/,
|
test: /\.([tj])sx?$/,
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
use: ['swc-loader'],
|
use: [ 'swc-loader' ],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
@ -34,17 +34,23 @@ module.exports = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
// allows us to do absolute imports from "src"
|
modules: [ path.join(__dirname, 'src'), 'node_modules' ],
|
||||||
modules: [path.join(__dirname, 'src'), 'node_modules'],
|
extensions: [ '*', '.js', '.jsx', '.ts', '.tsx' ],
|
||||||
extensions: ['*', '.js', '.jsx', '.ts', '.tsx'],
|
|
||||||
},
|
},
|
||||||
devtool: 'eval-source-map',
|
devtool: 'source-map',
|
||||||
devServer: {
|
devServer: {
|
||||||
contentBase: path.join(__dirname, 'dev'),
|
contentBase: path.join(__dirname, 'dev'),
|
||||||
historyApiFallback: true,
|
historyApiFallback: true,
|
||||||
hot: true,
|
hot: true,
|
||||||
port: 8000,
|
port: 8000,
|
||||||
host: '0.0.0.0',
|
host: '0.0.0.0',
|
||||||
|
allowedHosts: [
|
||||||
|
'localhost:3000',
|
||||||
|
'localhost:8000',
|
||||||
|
],
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
}
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.HotModuleReplacementPlugin(),
|
new webpack.HotModuleReplacementPlugin(),
|
||||||
|
@ -1123,6 +1123,50 @@
|
|||||||
"@nodelib/fs.scandir" "2.1.3"
|
"@nodelib/fs.scandir" "2.1.3"
|
||||||
fastq "^1.6.0"
|
fastq "^1.6.0"
|
||||||
|
|
||||||
|
"@redux-saga/core@^1.1.3":
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@redux-saga/core/-/core-1.1.3.tgz#3085097b57a4ea8db5528d58673f20ce0950f6a4"
|
||||||
|
integrity sha512-8tInBftak8TPzE6X13ABmEtRJGjtK17w7VUs7qV17S8hCO5S3+aUTWZ/DBsBJPdE8Z5jOPwYALyvofgq1Ws+kg==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.6.3"
|
||||||
|
"@redux-saga/deferred" "^1.1.2"
|
||||||
|
"@redux-saga/delay-p" "^1.1.2"
|
||||||
|
"@redux-saga/is" "^1.1.2"
|
||||||
|
"@redux-saga/symbols" "^1.1.2"
|
||||||
|
"@redux-saga/types" "^1.1.0"
|
||||||
|
redux "^4.0.4"
|
||||||
|
typescript-tuple "^2.2.1"
|
||||||
|
|
||||||
|
"@redux-saga/deferred@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@redux-saga/deferred/-/deferred-1.1.2.tgz#59937a0eba71fff289f1310233bc518117a71888"
|
||||||
|
integrity sha512-908rDLHFN2UUzt2jb4uOzj6afpjgJe3MjICaUNO3bvkV/kN/cNeI9PMr8BsFXB/MR8WTAZQq/PlTq8Kww3TBSQ==
|
||||||
|
|
||||||
|
"@redux-saga/delay-p@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@redux-saga/delay-p/-/delay-p-1.1.2.tgz#8f515f4b009b05b02a37a7c3d0ca9ddc157bb355"
|
||||||
|
integrity sha512-ojc+1IoC6OP65Ts5+ZHbEYdrohmIw1j9P7HS9MOJezqMYtCDgpkoqB5enAAZrNtnbSL6gVCWPHaoaTY5KeO0/g==
|
||||||
|
dependencies:
|
||||||
|
"@redux-saga/symbols" "^1.1.2"
|
||||||
|
|
||||||
|
"@redux-saga/is@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@redux-saga/is/-/is-1.1.2.tgz#ae6c8421f58fcba80faf7cadb7d65b303b97e58e"
|
||||||
|
integrity sha512-OLbunKVsCVNTKEf2cH4TYyNbbPgvmZ52iaxBD4I1fTif4+MTXMa4/Z07L83zW/hTCXwpSZvXogqMqLfex2Tg6w==
|
||||||
|
dependencies:
|
||||||
|
"@redux-saga/symbols" "^1.1.2"
|
||||||
|
"@redux-saga/types" "^1.1.0"
|
||||||
|
|
||||||
|
"@redux-saga/symbols@^1.1.2":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@redux-saga/symbols/-/symbols-1.1.2.tgz#216a672a487fc256872b8034835afc22a2d0595d"
|
||||||
|
integrity sha512-EfdGnF423glv3uMwLsGAtE6bg+R9MdqlHEzExnfagXPrIiuxwr3bdiAwz3gi+PsrQ3yBlaBpfGLtDG8rf3LgQQ==
|
||||||
|
|
||||||
|
"@redux-saga/types@^1.1.0":
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@redux-saga/types/-/types-1.1.0.tgz#0e81ce56b4883b4b2a3001ebe1ab298b84237204"
|
||||||
|
integrity sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==
|
||||||
|
|
||||||
"@samverschueren/stream-to-observable@^0.3.0":
|
"@samverschueren/stream-to-observable@^0.3.0":
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
|
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
|
||||||
@ -1140,6 +1184,13 @@
|
|||||||
progress "^2.0.3"
|
progress "^2.0.3"
|
||||||
"true-case-path" "^1.0.3"
|
"true-case-path" "^1.0.3"
|
||||||
|
|
||||||
|
"@types/axios@^0.14.0":
|
||||||
|
version "0.14.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/axios/-/axios-0.14.0.tgz#ec2300fbe7d7dddd7eb9d3abf87999964cafce46"
|
||||||
|
integrity sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=
|
||||||
|
dependencies:
|
||||||
|
axios "*"
|
||||||
|
|
||||||
"@types/babel__core@^7.1.0":
|
"@types/babel__core@^7.1.0":
|
||||||
version "7.1.6"
|
version "7.1.6"
|
||||||
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610"
|
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.6.tgz#16ff42a5ae203c9af1c6e190ed1f30f83207b610"
|
||||||
@ -1192,6 +1243,14 @@
|
|||||||
"@types/minimatch" "*"
|
"@types/minimatch" "*"
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/hoist-non-react-statics@^3.3.0":
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
|
||||||
|
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
|
||||||
|
dependencies:
|
||||||
|
"@types/react" "*"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
|
||||||
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
|
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
|
||||||
@ -1222,6 +1281,48 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.5.tgz#59738bf30b31aea1faa2df7f4a5f55613750cf00"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.9.5.tgz#59738bf30b31aea1faa2df7f4a5f55613750cf00"
|
||||||
integrity sha512-hkzMMD3xu6BrJpGVLeQ3htQQNAcOrJjX7WFmtK8zWQpz2UJf13LCFF2ALA7c9OVdvc2vQJeDdjfR35M0sBCxvw==
|
integrity sha512-hkzMMD3xu6BrJpGVLeQ3htQQNAcOrJjX7WFmtK8zWQpz2UJf13LCFF2ALA7c9OVdvc2vQJeDdjfR35M0sBCxvw==
|
||||||
|
|
||||||
|
"@types/prop-types@*":
|
||||||
|
version "15.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
|
||||||
|
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
|
||||||
|
|
||||||
|
"@types/react-redux@^7.1.7":
|
||||||
|
version "7.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.7.tgz#12a0c529aba660696947384a059c5c6e08185c7a"
|
||||||
|
integrity sha512-U+WrzeFfI83+evZE2dkZ/oF/1vjIYgqrb5dGgedkqVV8HEfDFujNgWCwHL89TDuWKb47U0nTBT6PLGq4IIogWg==
|
||||||
|
dependencies:
|
||||||
|
"@types/hoist-non-react-statics" "^3.3.0"
|
||||||
|
"@types/react" "*"
|
||||||
|
hoist-non-react-statics "^3.3.0"
|
||||||
|
redux "^4.0.0"
|
||||||
|
|
||||||
|
"@types/react@*":
|
||||||
|
version "16.9.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.27.tgz#7fc5db99e3ec3f21735b44d3560cff684856814a"
|
||||||
|
integrity sha512-j+RvQb9w7a2kZFBOgTh+s/elCwtqWUMN6RJNdmz0ntmwpeoMHKnyhUcmYBu7Yw94Rtj9938D+TJSn6WGcq2+OA==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
csstype "^2.2.0"
|
||||||
|
|
||||||
|
"@types/redux-actions@^2.6.1":
|
||||||
|
version "2.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.6.1.tgz#0940e97fa35ad3004316bddb391d8e01d2efa605"
|
||||||
|
integrity sha512-zKgK+ATp3sswXs6sOYo1tk8xdXTy4CTaeeYrVQlClCjeOpag5vzPo0ASWiiBJ7vsiQRAdb3VkuFLnDoBimF67g==
|
||||||
|
|
||||||
|
"@types/redux-saga@^0.10.5":
|
||||||
|
version "0.10.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/redux-saga/-/redux-saga-0.10.5.tgz#80bf21078379ebc97387dbe56e44467b5677fa85"
|
||||||
|
integrity sha1-gL8hB4N568lzh9vlbkRGe1Z3+oU=
|
||||||
|
dependencies:
|
||||||
|
redux-saga "*"
|
||||||
|
|
||||||
|
"@types/redux@^3.6.0":
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/redux/-/redux-3.6.0.tgz#f1ebe1e5411518072e4fdfca5c76e16e74c1399a"
|
||||||
|
integrity sha1-8evh5UEVGAcuT9/KXHbhbnTBOZo=
|
||||||
|
dependencies:
|
||||||
|
redux "*"
|
||||||
|
|
||||||
"@types/sizzle@2.3.2":
|
"@types/sizzle@2.3.2":
|
||||||
version "2.3.2"
|
version "2.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47"
|
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47"
|
||||||
@ -1770,7 +1871,7 @@ aws4@^1.8.0:
|
|||||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||||
|
|
||||||
axios@^0.19.0:
|
axios@*, axios@^0.19.2:
|
||||||
version "0.19.2"
|
version "0.19.2"
|
||||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
|
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
|
||||||
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
|
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
|
||||||
@ -2804,7 +2905,7 @@ cssstyle@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
cssom "0.3.x"
|
cssom "0.3.x"
|
||||||
|
|
||||||
csstype@^2.6.7:
|
csstype@^2.2.0, csstype@^2.6.7:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
|
||||||
integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
|
integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
|
||||||
@ -5734,6 +5835,11 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3:
|
|||||||
array-includes "^3.0.3"
|
array-includes "^3.0.3"
|
||||||
object.assign "^4.1.0"
|
object.assign "^4.1.0"
|
||||||
|
|
||||||
|
just-curry-it@^3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/just-curry-it/-/just-curry-it-3.1.0.tgz#ab59daed308a58b847ada166edd0a2d40766fbc5"
|
||||||
|
integrity sha512-mjzgSOFzlrurlURaHVjnQodyPNvrHrf1TbQP2XU9NSqBtHQPuHZ+Eb6TAJP7ASeJN9h9K0KXoRTs8u6ouHBKvg==
|
||||||
|
|
||||||
jwt-decode@^2.2.0:
|
jwt-decode@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79"
|
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79"
|
||||||
@ -7557,7 +7663,7 @@ react-is@^16.8.4, react-is@^16.9.0:
|
|||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||||
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
|
||||||
|
|
||||||
react-redux@^7.1.1:
|
react-redux@^7.1.1, react-redux@^7.2.0:
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
|
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.0.tgz#f970f62192b3981642fec46fd0db18a074fe879d"
|
||||||
integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==
|
integrity sha512-EvCAZYGfOLqwV7gh849xy9/pt55rJXPwmYvI4lilPM5rUT/1NxuuN59ipdBksRVSvz0KInbPnp4IfoXJXCqiDA==
|
||||||
@ -7696,7 +7802,30 @@ realpath-native@^1.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
util.promisify "^1.0.0"
|
util.promisify "^1.0.0"
|
||||||
|
|
||||||
redux@^4.0.4:
|
reduce-reducers@^0.4.3:
|
||||||
|
version "0.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.4.3.tgz#8e052618801cd8fc2714b4915adaa8937eb6d66c"
|
||||||
|
integrity sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw==
|
||||||
|
|
||||||
|
redux-actions@^2.6.5:
|
||||||
|
version "2.6.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.6.5.tgz#bdca548768ee99832a63910c276def85e821a27e"
|
||||||
|
integrity sha512-pFhEcWFTYNk7DhQgxMGnbsB1H2glqhQJRQrtPb96kD3hWiZRzXHwwmFPswg6V2MjraXRXWNmuP9P84tvdLAJmw==
|
||||||
|
dependencies:
|
||||||
|
invariant "^2.2.4"
|
||||||
|
just-curry-it "^3.1.0"
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
reduce-reducers "^0.4.3"
|
||||||
|
to-camel-case "^1.0.0"
|
||||||
|
|
||||||
|
redux-saga@*, redux-saga@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/redux-saga/-/redux-saga-1.1.3.tgz#9f3e6aebd3c994bbc0f6901a625f9a42b51d1112"
|
||||||
|
integrity sha512-RkSn/z0mwaSa5/xH/hQLo8gNf4tlvT18qXDNvedihLcfzh+jMchDgaariQoehCpgRltEm4zHKJyINEz6aqswTw==
|
||||||
|
dependencies:
|
||||||
|
"@redux-saga/core" "^1.1.3"
|
||||||
|
|
||||||
|
redux@*, redux@^4.0.0, redux@^4.0.4, redux@^4.0.5:
|
||||||
version "4.0.5"
|
version "4.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
||||||
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
||||||
@ -8966,11 +9095,23 @@ to-arraybuffer@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
|
resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
|
||||||
integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
|
integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=
|
||||||
|
|
||||||
|
to-camel-case@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/to-camel-case/-/to-camel-case-1.0.0.tgz#1a56054b2f9d696298ce66a60897322b6f423e46"
|
||||||
|
integrity sha1-GlYFSy+daWKYzmamCJcyK29CPkY=
|
||||||
|
dependencies:
|
||||||
|
to-space-case "^1.0.0"
|
||||||
|
|
||||||
to-fast-properties@^2.0.0:
|
to-fast-properties@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
||||||
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
|
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
|
||||||
|
|
||||||
|
to-no-case@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a"
|
||||||
|
integrity sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo=
|
||||||
|
|
||||||
to-object-path@^0.3.0:
|
to-object-path@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
|
resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
|
||||||
@ -9003,6 +9144,13 @@ to-regex@^3.0.1, to-regex@^3.0.2:
|
|||||||
regex-not "^1.0.2"
|
regex-not "^1.0.2"
|
||||||
safe-regex "^1.1.0"
|
safe-regex "^1.1.0"
|
||||||
|
|
||||||
|
to-space-case@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17"
|
||||||
|
integrity sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc=
|
||||||
|
dependencies:
|
||||||
|
to-no-case "^1.0.0"
|
||||||
|
|
||||||
toidentifier@1.0.0:
|
toidentifier@1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
|
||||||
@ -9100,6 +9248,25 @@ typedarray@^0.0.6:
|
|||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||||
|
|
||||||
|
typescript-compare@^0.0.2:
|
||||||
|
version "0.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript-compare/-/typescript-compare-0.0.2.tgz#7ee40a400a406c2ea0a7e551efd3309021d5f425"
|
||||||
|
integrity sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==
|
||||||
|
dependencies:
|
||||||
|
typescript-logic "^0.0.0"
|
||||||
|
|
||||||
|
typescript-logic@^0.0.0:
|
||||||
|
version "0.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript-logic/-/typescript-logic-0.0.0.tgz#66ebd82a2548f2b444a43667bec120b496890196"
|
||||||
|
integrity sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q==
|
||||||
|
|
||||||
|
typescript-tuple@^2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript-tuple/-/typescript-tuple-2.2.1.tgz#7d9813fb4b355f69ac55032e0363e8bb0f04dad2"
|
||||||
|
integrity sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==
|
||||||
|
dependencies:
|
||||||
|
typescript-compare "^0.0.2"
|
||||||
|
|
||||||
uglify-js@3.4.x:
|
uglify-js@3.4.x:
|
||||||
version "3.4.10"
|
version "3.4.10"
|
||||||
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
|
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.4.10.tgz#9ad9563d8eb3acdfb8d38597d2af1d815f6a755f"
|
||||||
|
Loading…
Reference in New Issue
Block a user