diff --git a/Cargo.lock b/Cargo.lock
index 3b135a73..729011ee 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -240,7 +240,7 @@ dependencies = [
"proc-macro2",
"quote",
"strum",
- "syn 2.0.92",
+ "syn 2.0.96",
"thiserror 1.0.69",
]
@@ -287,7 +287,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -298,13 +298,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-trait"
-version = "0.1.83"
+version = "0.1.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
+checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -318,18 +318,6 @@ dependencies = [
"rustc_version",
]
-[[package]]
-name = "auto_enums"
-version = "0.8.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "459b77b7e855f875fd15f101064825cd79eb83185a961d66e6298560126facfb"
-dependencies = [
- "derive_utils",
- "proc-macro2",
- "quote",
- "syn 2.0.92",
-]
-
[[package]]
name = "autocfg"
version = "1.4.0"
@@ -348,8 +336,6 @@ dependencies = [
"email_address",
"handlebars",
"jsonwebtoken",
- "juniper",
- "juniper_axum",
"lettre",
"rand",
"rust-i18n",
@@ -367,14 +353,13 @@ dependencies = [
[[package]]
name = "axum"
-version = "0.7.9"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
+checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8"
dependencies = [
- "async-trait",
"axum-core",
- "base64 0.22.1",
"bytes",
+ "form_urlencoded",
"futures-util",
"http",
"http-body",
@@ -393,10 +378,8 @@ dependencies = [
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
- "sha1",
"sync_wrapper",
"tokio",
- "tokio-tungstenite 0.24.0",
"tower",
"tower-layer",
"tower-service",
@@ -405,11 +388,10 @@ dependencies = [
[[package]]
name = "axum-core"
-version = "0.4.5"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
+checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733"
dependencies = [
- "async-trait",
"bytes",
"futures-util",
"http",
@@ -426,21 +408,19 @@ dependencies = [
[[package]]
name = "axum-extra"
-version = "0.9.6"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04"
+checksum = "460fc6f625a1f7705c6cf62d0d070794e94668988b1c38111baeec177c715f7b"
dependencies = [
"axum",
"axum-core",
"bytes",
"cookie",
- "fastrand",
"futures-util",
"http",
"http-body",
"http-body-util",
"mime",
- "multer",
"pin-project-lite",
"serde",
"tower",
@@ -465,9 +445,12 @@ dependencies = [
[[package]]
name = "base62"
-version = "2.0.3"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48fa474cf7492f9a299ba6019fb99ec673e1739556d48e8a90eabaea282ef0e4"
+checksum = "3d2c393c12f2661ef3f22426d26f95bcfa13d730abab7554502f427562b72f37"
+dependencies = [
+ "rustversion",
+]
[[package]]
name = "base64"
@@ -515,7 +498,7 @@ version = "0.69.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.7.0",
"cexpr",
"clang-sys",
"itertools 0.12.1",
@@ -526,7 +509,7 @@ dependencies = [
"regex",
"rustc-hash 1.1.0",
"shlex",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -552,9 +535,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.6.0"
+version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be"
[[package]]
name = "bitvec"
@@ -629,14 +612,14 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
name = "bstr"
-version = "1.11.1"
+version = "1.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8"
+checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0"
dependencies = [
"memchr",
"serde",
@@ -713,9 +696,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.2.6"
+version = "1.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333"
+checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b"
dependencies = [
"jobserver",
"libc",
@@ -1000,7 +983,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -1011,7 +994,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -1044,14 +1027,34 @@ dependencies = [
]
[[package]]
-name = "derive_utils"
-version = "0.14.2"
+name = "derive_builder"
+version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65f152f4b8559c4da5d574bafc7af85454d706b4c5fe8b530d508cacbb6807ea"
+checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
dependencies = [
+ "derive_builder_macro",
+]
+
+[[package]]
+name = "derive_builder_core"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
+dependencies = [
+ "darling",
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "derive_builder_macro"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
+dependencies = [
+ "derive_builder_core",
+ "syn 2.0.96",
]
[[package]]
@@ -1100,7 +1103,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -1201,9 +1204,9 @@ dependencies = [
[[package]]
name = "event-listener"
-version = "5.3.1"
+version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
+checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae"
dependencies = [
"concurrent-queue",
"parking",
@@ -1359,9 +1362,9 @@ checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-lite"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1"
+checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532"
dependencies = [
"fastrand",
"futures-core",
@@ -1378,7 +1381,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -1497,9 +1500,9 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "glob"
-version = "0.3.1"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "globset"
@@ -1537,17 +1540,18 @@ dependencies = [
[[package]]
name = "handlebars"
-version = "6.2.0"
+version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd4ccde012831f9a071a637b0d4e31df31c0f6c525784b35ae76a9ac6bc1e315"
+checksum = "3d6b224b95c1e668ac0270325ad563b2eef1469fbbb8959bc7c692c844b813d9"
dependencies = [
+ "derive_builder",
"log",
"num-order",
"pest",
"pest_derive",
"serde",
"serde_json",
- "thiserror 1.0.69",
+ "thiserror 2.0.11",
]
[[package]]
@@ -1654,7 +1658,7 @@ dependencies = [
"markup5ever",
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -1910,7 +1914,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -2069,72 +2073,6 @@ dependencies = [
"simple_asn1",
]
-[[package]]
-name = "juniper"
-version = "0.16.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "943306315b1a7a03d27af9dfb0c288d9f4da8830c17df4bceb7d50a47da0982c"
-dependencies = [
- "async-trait",
- "auto_enums",
- "fnv",
- "futures",
- "indexmap 2.7.0",
- "juniper_codegen",
- "serde",
- "smartstring",
- "static_assertions",
-]
-
-[[package]]
-name = "juniper_axum"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed3b21b9af313a2967572c8d4b8875c53fc8062e10768470de4748c16ce7b992"
-dependencies = [
- "axum",
- "bytes",
- "futures",
- "juniper",
- "juniper_graphql_ws",
- "serde",
- "serde_json",
-]
-
-[[package]]
-name = "juniper_codegen"
-version = "0.16.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "760dbe46660494d469023d661e8d268f413b2cb68c999975dcc237407096a693"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 2.0.92",
- "url",
-]
-
-[[package]]
-name = "juniper_graphql_ws"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "709eb11c716072f5c9fcbfa705dd684bd3c070943102f9fc56ccb812a36ba017"
-dependencies = [
- "juniper",
- "juniper_subscriptions",
- "serde",
- "tokio",
-]
-
-[[package]]
-name = "juniper_subscriptions"
-version = "0.17.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6208a839bd4ca2131924a238311d088d6604ea267c0917903392bad7b70a92c"
-dependencies = [
- "futures",
- "juniper",
-]
-
[[package]]
name = "lalrpop"
version = "0.20.2"
@@ -2243,7 +2181,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.7.0",
"libc",
]
@@ -2271,9 +2209,9 @@ checksum = "64804cc6a5042d4f05379909ba25b503ec04e2c082151d62122d5dcaa274b961"
[[package]]
name = "libz-sys"
-version = "1.1.20"
+version = "1.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2d16453e800a8cf6dd2fc3eb4bc99b786a9b90c663b8559a5b1a041bf89e472"
+checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa"
dependencies = [
"cc",
"pkg-config",
@@ -2294,9 +2232,9 @@ dependencies = [
[[package]]
name = "linux-raw-sys"
-version = "0.4.14"
+version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "litemap"
@@ -2376,9 +2314,9 @@ dependencies = [
[[package]]
name = "matchit"
-version = "0.7.3"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
+checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "matrixmultiply"
@@ -2426,7 +2364,7 @@ checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -2707,7 +2645,7 @@ version = "0.10.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.7.0",
"cfg-if",
"foreign-types",
"libc",
@@ -2724,7 +2662,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -2838,7 +2776,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc"
dependencies = [
"memchr",
- "thiserror 2.0.9",
+ "thiserror 2.0.11",
"ucd-trie",
]
@@ -2862,7 +2800,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -2898,22 +2836,22 @@ dependencies = [
[[package]]
name = "phf"
-version = "0.11.2"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
+checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_macros",
- "phf_shared 0.11.2",
+ "phf_shared 0.11.3",
]
[[package]]
name = "phf_codegen"
-version = "0.11.2"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
+checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [
- "phf_generator 0.11.2",
- "phf_shared 0.11.2",
+ "phf_generator 0.11.3",
+ "phf_shared 0.11.3",
]
[[package]]
@@ -2928,25 +2866,25 @@ dependencies = [
[[package]]
name = "phf_generator"
-version = "0.11.2"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
+checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
- "phf_shared 0.11.2",
+ "phf_shared 0.11.3",
"rand",
]
[[package]]
name = "phf_macros"
-version = "0.11.2"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
+checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
dependencies = [
- "phf_generator 0.11.2",
- "phf_shared 0.11.2",
+ "phf_generator 0.11.3",
+ "phf_shared 0.11.3",
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
"unicase",
]
@@ -2961,11 +2899,11 @@ dependencies = [
[[package]]
name = "phf_shared"
-version = "0.11.2"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
+checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
- "siphasher 0.3.11",
+ "siphasher 1.0.1",
"unicase",
]
@@ -2977,9 +2915,9 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project-lite"
-version = "0.2.15"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
@@ -3025,9 +2963,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.92"
+version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
@@ -3104,7 +3042,7 @@ dependencies = [
"rustc-hash 2.1.0",
"rustls",
"socket2",
- "thiserror 2.0.9",
+ "thiserror 2.0.11",
"tokio",
"tracing",
]
@@ -3123,7 +3061,7 @@ dependencies = [
"rustls",
"rustls-pki-types",
"slab",
- "thiserror 2.0.9",
+ "thiserror 2.0.11",
"tinyvec",
"tracing",
"web-time",
@@ -3243,7 +3181,7 @@ version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.7.0",
]
[[package]]
@@ -3274,7 +3212,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -3332,9 +3270,9 @@ dependencies = [
[[package]]
name = "reqwest"
-version = "0.12.11"
+version = "0.12.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fe060fe50f524be480214aba758c71f99f90ee8c83c5a36b5e9e1d568eb4eb3"
+checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
dependencies = [
"base64 0.22.1",
"bytes",
@@ -3399,7 +3337,7 @@ checksum = "5f0ec466e5d8dca9965eb6871879677bef5590cf7525ad96cae14376efb75073"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -3480,9 +3418,9 @@ dependencies = [
[[package]]
name = "roaring"
-version = "0.10.9"
+version = "0.10.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41589aba99537475bf697f2118357cad1c31590c5a1b9f6d9fc4ad6d07503661"
+checksum = "a652edd001c53df0b3f96a36a8dc93fce6866988efc16808235653c6bcac8bf2"
dependencies = [
"bytemuck",
"byteorder",
@@ -3544,7 +3482,7 @@ dependencies = [
"serde",
"serde_json",
"serde_yml",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -3634,11 +3572,11 @@ dependencies = [
[[package]]
name = "rustix"
-version = "0.38.42"
+version = "0.38.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
+checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6"
dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.7.0",
"errno",
"libc",
"linux-raw-sys",
@@ -3647,9 +3585,9 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.23.20"
+version = "0.23.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b"
+checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
dependencies = [
"log",
"once_cell",
@@ -3758,7 +3696,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.7.0",
"core-foundation",
"core-foundation-sys",
"libc",
@@ -3767,9 +3705,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.13.0"
+version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1863fd3768cd83c56a7f60faa4dc0d403f1b6df0a38c3c25f44b7894e45370d5"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
@@ -3816,14 +3754,14 @@ checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
name = "serde_json"
-version = "1.0.134"
+version = "1.0.135"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
+checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9"
dependencies = [
"indexmap 2.7.0",
"itoa",
@@ -3890,7 +3828,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -4001,17 +3939,6 @@ version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
-[[package]]
-name = "smartstring"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29"
-dependencies = [
- "autocfg",
- "static_assertions",
- "version_check",
-]
-
[[package]]
name = "smol_str"
version = "0.2.2"
@@ -4096,12 +4023,6 @@ dependencies = [
"windows-sys 0.59.0",
]
-[[package]]
-name = "static_assertions"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-
[[package]]
name = "static_assertions_next"
version = "1.1.2"
@@ -4171,7 +4092,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -4210,7 +4131,7 @@ dependencies = [
"surrealdb-core",
"thiserror 1.0.69",
"tokio",
- "tokio-tungstenite 0.23.1",
+ "tokio-tungstenite",
"tokio-util",
"tracing",
"trice",
@@ -4355,9 +4276,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.92"
+version = "2.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ae51629bf965c5c098cc9e87908a3df5301051a9e087d6f9bef5c9771ed126"
+checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
dependencies = [
"proc-macro2",
"quote",
@@ -4381,7 +4302,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -4392,12 +4313,13 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
-version = "3.14.0"
+version = "3.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
+checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
dependencies = [
"cfg-if",
"fastrand",
+ "getrandom",
"once_cell",
"rustix",
"windows-sys 0.59.0",
@@ -4436,11 +4358,11 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.9"
+version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
+checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
dependencies = [
- "thiserror-impl 2.0.9",
+ "thiserror-impl 2.0.11",
]
[[package]]
@@ -4451,18 +4373,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.9"
+version = "2.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
+checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -4542,9 +4464,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.42.0"
+version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
+checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [
"backtrace",
"bytes",
@@ -4560,13 +4482,13 @@ dependencies = [
[[package]]
name = "tokio-macros"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -4601,22 +4523,10 @@ dependencies = [
"rustls-pki-types",
"tokio",
"tokio-rustls",
- "tungstenite 0.23.0",
+ "tungstenite",
"webpki-roots",
]
-[[package]]
-name = "tokio-tungstenite"
-version = "0.24.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
-dependencies = [
- "futures-util",
- "log",
- "tokio",
- "tungstenite 0.24.0",
-]
-
[[package]]
name = "tokio-util"
version = "0.7.13"
@@ -4673,7 +4583,7 @@ checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
dependencies = [
"indexmap 2.7.0",
"toml_datetime",
- "winnow 0.6.20",
+ "winnow 0.6.24",
]
[[package]]
@@ -4698,7 +4608,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697"
dependencies = [
- "bitflags 2.6.0",
+ "bitflags 2.7.0",
"bytes",
"futures-util",
"http",
@@ -4749,7 +4659,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -4840,24 +4750,6 @@ dependencies = [
"utf-8",
]
-[[package]]
-name = "tungstenite"
-version = "0.24.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
-dependencies = [
- "byteorder",
- "bytes",
- "data-encoding",
- "http",
- "httparse",
- "log",
- "rand",
- "sha1",
- "thiserror 1.0.69",
- "utf-8",
-]
-
[[package]]
name = "typenum"
version = "1.17.0"
@@ -4872,11 +4764,10 @@ checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "ulid"
-version = "1.1.3"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04f903f293d11f31c0c29e4148f6dc0d033a7f80cebc0282bea147611667d289"
+checksum = "f294bff79170ed1c5633812aff1e565c35d993a36e757f9bc0accf5eec4e6045"
dependencies = [
- "getrandom",
"rand",
"serde",
"web-time",
@@ -4974,9 +4865,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utoipa"
-version = "5.3.0"
+version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68e76d357bc95c7d0939c92c04c9269871a8470eea39cb1f0231eeadb0c47d0f"
+checksum = "435c6f69ef38c9017b4b4eea965dfb91e71e53d869e896db40d1cf2441dd75c0"
dependencies = [
"indexmap 2.7.0",
"serde",
@@ -4986,20 +4877,20 @@ dependencies = [
[[package]]
name = "utoipa-gen"
-version = "5.3.0"
+version = "5.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "564b03f8044ad6806bdc0d635e88be24967e785eef096df6b2636d2cc1e05d4b"
+checksum = "a77d306bc75294fd52f3e99b13ece67c02c1a2789190a6f31d32f736624326f7"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
name = "uuid"
-version = "1.11.0"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
+checksum = "b913a3b5fe84142e269d63cc62b64319ccaf89b748fc31fe025177f767a756c4"
dependencies = [
"getrandom",
"serde",
@@ -5076,7 +4967,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
"wasm-bindgen-shared",
]
@@ -5111,7 +5002,7 @@ checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@@ -5350,9 +5241,9 @@ dependencies = [
[[package]]
name = "winnow"
-version = "0.6.20"
+version = "0.6.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
+checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
dependencies = [
"memchr",
]
@@ -5417,7 +5308,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
"synstructure",
]
@@ -5439,7 +5330,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
@@ -5459,7 +5350,7 @@ checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
"synstructure",
]
@@ -5488,7 +5379,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.92",
+ "syn 2.0.96",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index 98224b23..f4d3747e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,28 +6,26 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-axum = { version = "0.7.9", features = ["multipart"] }
-serde = { version = "1.0.216", features = ["derive"] }
-tokio = { version = "1.42.0", features = ["full"] }
+axum = { version = "0.8.1", features = ["multipart"] }
+serde = { version = "1.0.217", features = ["derive"] }
+tokio = { version = "1.43.0", features = ["full"] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
tower-http = { version = "0.6.2", features = ["fs", "cors"] }
dotenvy = "0.15.7"
-axum-extra = { version = "0.9.6", features = ["cookie", "cookie-signed"] }
+axum-extra = { version = "0.10.0", features = ["cookie", "cookie-signed"] }
argon2 = "0.5.3"
rand = "0.8.5"
urlencoding = "2.1.3"
-serde_json = "1.0.134"
+serde_json = "1.0.135"
surrealdb = { version = "2.1.4", features = ["kv-rocksdb", "kv-mem"] }
jsonwebtoken = "9.3.0"
chrono = { version = "0.4.39", features = [] }
email_address = "0.2.9"
rust-i18n = "3.1.2"
lettre = { version = "0.11.11", features = ["tokio1-native-tls"] }
-handlebars = "6.2.0"
-utoipa = "5.3.0"
-juniper = "0.16.1"
-juniper_axum = { version = "0.1.1", features = ["subscriptions"] }
+handlebars = "6.3.0"
+utoipa = "5.3.1"
[dev-dependencies]
tower = { version = "0.5.2", features = ["util"] }
diff --git a/react-admin/src/App.tsx b/react-admin/src/App.tsx
index 1b7f69e1..57b977fe 100644
--- a/react-admin/src/App.tsx
+++ b/react-admin/src/App.tsx
@@ -24,10 +24,15 @@ import AvoRedApiTesting from "./pages/setting/AvoRedApiTesting";
import SettingPage from "./pages/setting/SettingPage";
import NotFoundPage from "./pages/NotFoundPage";
import ChangePassword from './pages/admin-user/ChangePassword';
-import { ModelTablePage } from "./pages/models/ModelTablePage";
import {ModelCreatePage} from "./pages/models/ModelCreatePage";
import { ModelEditPage } from "./pages/models/ModelEditPage";
import {ComponentTablePage} from "./pages/component/ComponentTablePage";
+import {CollectionTable} from "./pages/collection/CollectionTable";
+import {CollectionEdit} from "./pages/collection/CollectionEdit";
+import {CollectionCreate} from "./pages/collection/CollectionCreate";
+import {ContentTable} from "./pages/content/ContentTable";
+import {ContentCreate} from "./pages/content/ContentCreate";
+import {ContentEdit} from "./pages/content/ContentEdit";
function App() {
return (
@@ -51,9 +56,16 @@ function App() {
} />
} />
} />
- } />
+
+ } />
+ } />
+ } />
+
} />
} />
+ } />
+ } />
+ } />
} />
} />
} />
diff --git a/react-admin/src/layouts/partials/AppSidebar.tsx b/react-admin/src/layouts/partials/AppSidebar.tsx
index f9e0336e..8bbfecac 100644
--- a/react-admin/src/layouts/partials/AppSidebar.tsx
+++ b/react-admin/src/layouts/partials/AppSidebar.tsx
@@ -1,7 +1,14 @@
import {Link, Outlet} from "react-router-dom";
-import {Menu} from "@headlessui/react";
+import {Menu, MenuButton, MenuItem, MenuItems} from "@headlessui/react";
import {useTranslation} from "react-i18next";
-import {ChevronDownIcon, FilmIcon, RocketLaunchIcon, CpuChipIcon, DeviceTabletIcon} from "@heroicons/react/24/solid";
+import {
+ ChevronDownIcon,
+ FilmIcon,
+ RocketLaunchIcon,
+ CpuChipIcon,
+ DeviceTabletIcon,
+ CircleStackIcon
+} from "@heroicons/react/24/solid";
import {useContext} from "react";
import {ThemeContext} from "../../context/ThemeContext";
@@ -41,6 +48,15 @@ function AppSidebar() {
{t("sidebar.page")}
+
+
+
+
+ {t("collections")}
+
- {t("model")}
+ {t("content")}
@@ -79,7 +95,7 @@ function AppSidebar() {
diff --git a/react-admin/src/locales/en.json b/react-admin/src/locales/en.json
index 219daad9..c0f7b309 100644
--- a/react-admin/src/locales/en.json
+++ b/react-admin/src/locales/en.json
@@ -67,6 +67,7 @@
"table": "Table",
"permissions": "Permissions",
"page": "Page",
+ "collections": "Collections",
"role": "Role",
"general": "General",
"is_super_admin": "Is super admin",
@@ -90,8 +91,10 @@
"loading": "Loading...",
"component_information": "Component information",
"page_information": "Page Information",
- "model": "Model",
+ "content": "Content",
+ "content_field": "content field",
"model_information": "Model Information",
+ "collection_information": "Collection Information",
"components": "Components",
"field_type": "Field type",
"upload_asset": "Upload asset",
diff --git a/react-admin/src/pages/collection/CollectionCreate.tsx b/react-admin/src/pages/collection/CollectionCreate.tsx
new file mode 100644
index 00000000..44ab1fdc
--- /dev/null
+++ b/react-admin/src/pages/collection/CollectionCreate.tsx
@@ -0,0 +1,227 @@
+import {Link} from "react-router-dom";
+import InputField from "../../components/InputField";
+import {useTranslation} from "react-i18next";
+import {Controller, useFieldArray, useForm} from "react-hook-form";
+import {joiResolver} from "@hookform/resolvers/joi";
+import ErrorMessage from "../../components/ErrorMessage";
+import {
+ CollectionFieldDataType, CollectionFieldFieldType,
+ SavableCollectionType
+} from "../../types/collection/CreatableCollectionType";
+import {useStoreCollection} from "./hooks/useStoreCollection";
+import {useCollectionCreateSchema} from "./schemas/CollectionCreateSchema";
+import React, {useState} from "react";
+import slug from "slug";
+import AvoRedButton, {ButtonType} from "../../components/AvoRedButton";
+import _ from "lodash";
+import {CollectionFieldModal} from "./CollectionFieldModal";
+import {Cog8ToothIcon, TrashIcon} from "@heroicons/react/24/solid";
+
+export const CollectionCreate = () => {
+ const [t] = useTranslation("global")
+ const {mutate, error} = useStoreCollection()
+ const [isCollectionFieldModalOpen, setIsCollectionFieldModalOpen] = useState(false);
+ const [currentIndex, setCurrentIndex] = useState(0);
+
+ const {
+ register,
+ handleSubmit,
+ setValue,
+ formState: {errors},
+ getValues,
+ control,
+ trigger
+ } = useForm({
+ resolver: joiResolver(useCollectionCreateSchema(), {allowUnknown: true}),
+ })
+
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: "collection_fields", //rename fields
+ });
+
+ const submitHandler = ((data: SavableCollectionType) => {
+ console.log(data)
+ // mutate(data)
+ })
+
+ const onNameChange = (e: React.KeyboardEvent) => {
+ setValue('identifier', slug(e.currentTarget.value || ''))
+ }
+
+ const deleteCollectionFieldOnClick = (e: any, index: number) => {
+ e.preventDefault();
+ remove(index);
+ setCurrentIndex(0);
+ };
+
+ const addFieldButtonOnClick = (async (e: React.MouseEvent, max_index: number) => {
+ e.preventDefault()
+ append({
+ name: '',
+ identifier: '',
+ data_type: CollectionFieldDataType.TEXT,
+ field_type: CollectionFieldFieldType.TEXT,
+ })
+ await trigger("collection_fields");
+ setCurrentIndex(max_index);
+ setIsCollectionFieldModalOpen(true)
+ })
+
+
+ return (
+ <>
+
+
+
+
+ {t("collection_information")}
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/react-admin/src/pages/collection/CollectionEdit.tsx b/react-admin/src/pages/collection/CollectionEdit.tsx
new file mode 100644
index 00000000..e3e1bb54
--- /dev/null
+++ b/react-admin/src/pages/collection/CollectionEdit.tsx
@@ -0,0 +1,135 @@
+import InputField from "../../components/InputField";
+import {Link, useParams} from "react-router-dom";
+import {useTranslation} from "react-i18next";
+import {useState} from "react";
+import {useForm} from "react-hook-form";
+import {PutCollectionIdentifierType} from "../../types/collection/PutCollectionIdentifierType";
+import {joiResolver} from "@hookform/resolvers/joi";
+import EditableCollectionType from "../../types/collection/EditableCollectionType";
+import {useGetCollection} from "./hooks/useGetCollection";
+import {useCollectionPutSchema} from "./schemas/CollectionPutSchema";
+import {useCollectionEditSchema} from "./schemas/CollectionEditSchema";
+import {usePutCollectionIdentifier} from "./hooks/usePutCollectionIdentifier";
+import {useUpdateCollection} from "./hooks/useUpdateCollection";
+
+export const CollectionEdit = (() => {
+ const params = useParams();
+ const collection_id = params.collection_id ?? ''
+ const {mutate} = useUpdateCollection(collection_id);
+ const [t] = useTranslation("global")
+ const {data} = useGetCollection(collection_id)
+ const [isEditableIdentifier, setIsEditableIdentifier] = useState(true)
+ const values = data?.data.data
+
+
+ const {
+ register: putCollectionRegister,
+ getValues: getCollectionIdentifierValue
+ } = useForm({
+ resolver: joiResolver(useCollectionPutSchema(), {allowUnknown: true}),
+ values: {
+ identifier: data?.data.data.identifier
+ }
+ });
+
+ const {
+ register,
+ handleSubmit,
+ formState: {errors},
+ } = useForm({
+ resolver: joiResolver(useCollectionEditSchema(), {allowUnknown: true}),
+ values
+ })
+
+ const {mutate: putCollectionIdentifierMutate} = usePutCollectionIdentifier(collection_id)
+
+
+ const editableIdentifierOnClick = (() => {
+ setIsEditableIdentifier(false)
+ })
+ const saveIdentifierOnClick = (() => {
+ putCollectionIdentifierMutate({identifier: getCollectionIdentifierValue('identifier')})
+ setIsEditableIdentifier(true)
+ console.log("teststes")
+ })
+
+ const cancelIdentifierOnClick = (() => {
+ setIsEditableIdentifier(true)
+ })
+
+ const submitHandler = ((data: EditableCollectionType) => {
+ mutate(data)
+ })
+ return (
+ <>
+
+
+
+
+ {t("collection_information")}
+
+
+
+
+
+
+ >
+ )
+})
\ No newline at end of file
diff --git a/react-admin/src/pages/collection/CollectionFieldModal.tsx b/react-admin/src/pages/collection/CollectionFieldModal.tsx
new file mode 100644
index 00000000..5f8d1920
--- /dev/null
+++ b/react-admin/src/pages/collection/CollectionFieldModal.tsx
@@ -0,0 +1,125 @@
+import {
+ UseFormGetValues,
+ UseFormRegister,
+ UseFormSetValue,
+ UseFormTrigger,
+} from "react-hook-form";
+import AvoredModal from "../../components/AvoredModal";
+import AvoRedButton from "../../components/AvoRedButton";
+import { useTranslation } from "react-i18next";
+import InputField from "../../components/InputField";
+import React from "react";
+import slug from "slug";
+import {
+ CollectionFieldDataType,
+ SavableCollectionType, CollectionFieldFieldType
+} from "../../types/collection/CreatableCollectionType";
+
+type CollectionFieldProps = {
+ register: UseFormRegister;
+ currentIndex: number;
+ getValues: UseFormGetValues;
+ setValue: UseFormSetValue;
+ trigger: UseFormTrigger;
+ setIsOpen: React.Dispatch>;
+ isOpen: boolean;
+};
+
+export const CollectionFieldModal = ({
+ register,
+ currentIndex,
+ getValues,
+ setValue,
+ trigger,
+ setIsOpen,
+ isOpen,
+}: CollectionFieldProps) => {
+ const [t] = useTranslation("global");
+
+ const onContentFieldChange = async (
+ index: number,
+ field_type: CollectionFieldFieldType,
+ data_type: CollectionFieldDataType
+ ) => {
+ setValue(`collection_fields.${index}.field_type`, field_type);
+ setValue(`collection_fields.${index}.data_type`, data_type);
+ await trigger(`collection_fields.${index}`);
+ };
+
+ const contentFieldNameInputChange = ((e: React.KeyboardEvent, index: number) => {
+ e.stopPropagation();
+
+ setValue(`collection_fields.${index}.identifier`, slug(e.currentTarget.value));
+ })
+
+ return (
+ setIsOpen(false)}
+ modal_body={
+
+
+
+
+ ) => contentFieldNameInputChange(e, currentIndex)}
+ register={register(`collection_fields.${currentIndex}.name`)}
+ />
+
+
+
+
+
+ {/*
{renderFieldData(currentIndex)}
*/}
+
+
+
+
+
+ onContentFieldChange(
+ currentIndex,
+ CollectionFieldFieldType.TEXT,
+ CollectionFieldDataType.TEXT
+ )
+ }
+ className={`${getValues(`collection_fields.${currentIndex}.field_type`) === CollectionFieldFieldType.TEXT ? "bg-primary-200" : "bg-gray-300"}
+ ring-1 ring-gray-300 hover:cursor-pointer hover:ring-primary-300 p-3 rounded`}
+ >
+ {t("text_field")}
+
+
+
+
+
+
+
+
+
setIsOpen(false)}
+ className="bg-primary-500"
+ label={t("create_content_field")}
+ />
+
+
+
setIsOpen(false)}
+ label={t("cancel")}
+ />
+
+
+
+
+ }
+ modal_header={t("collection_field")}
+ isOpen={isOpen}
+ >
+ );
+};
diff --git a/react-admin/src/pages/collection/CollectionTable.tsx b/react-admin/src/pages/collection/CollectionTable.tsx
new file mode 100644
index 00000000..21c35f7b
--- /dev/null
+++ b/react-admin/src/pages/collection/CollectionTable.tsx
@@ -0,0 +1,131 @@
+import HasPermission from "../../components/HasPermission"
+import {Link} from "react-router-dom"
+import {useTranslation} from "react-i18next"
+import {useState} from "react"
+import {createColumnHelper, getCoreRowModel, SortingState, useReactTable} from "@tanstack/react-table"
+import AvoRedTable from "../../components/AvoRedTable"
+import {getFormattedDate} from "../../lib/common"
+import _ from "lodash"
+import {useCollectionTable} from "./hooks/useCollectionTable";
+import {CollectionType} from "../../types/collection/CollectionType";
+
+export const CollectionTable = (() => {
+ const [t] = useTranslation("global")
+ const [sorting, setSorting] = useState([]);
+ const [pagination, setPagination] = useState({
+ pageIndex: 0, //initial page index
+ pageSize: 10, //default page size
+ });
+
+ const customSorting = ((sorting: any) => {
+ setSorting(sorting)
+ })
+
+ const collection_api_table_response = useCollectionTable({
+ order: sorting.map((s) => `${s.id}:${s.desc ? 'DESC' : 'ASC'}`).join(','),
+ page: pagination.pageIndex
+ });
+ const customPagination = (async (pagination: any) => {
+ setPagination(pagination)
+ })
+ const collections: Array = _.get(collection_api_table_response, 'data.data.data', [])
+
+ const columnHelper = createColumnHelper()
+ const columns = [
+ columnHelper.accessor('id', {
+ cell: info => info.getValue(),
+ header: t("id")
+ }),
+ columnHelper.accessor('name', {
+ cell: info => info.getValue(),
+ header: t("name")
+ }),
+ columnHelper.accessor('identifier', {
+ cell: info => info.getValue(),
+ header: t("identifier")
+ }),
+ columnHelper.accessor('created_at', {
+ id: "created_at",
+ cell: info => getFormattedDate(info.getValue()),
+ header: t("created_at")
+ }),
+ columnHelper.accessor('created_by', {
+ cell: info => info.getValue(),
+ header: t("created_by")
+ }),
+ columnHelper.accessor('updated_at', {
+ cell: info => getFormattedDate(info.getValue()),
+ header: t("updated_at")
+ }),
+ columnHelper.accessor('updated_by', {
+ cell: info => info.getValue(),
+ header: t("updated_by")
+ }),
+ columnHelper.accessor('action', {
+ cell: info => {
+ return (
+
+
+ {t("edit")}
+
+
+ )
+ },
+ enableSorting: false,
+ header: t("action"),
+ enableHiding: false
+ }),
+ ]
+ const table = useReactTable({
+ data: collections,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ rowCount: collection_api_table_response.data?.data.pagination.total,
+ onPaginationChange: customPagination,
+ manualPagination: true,
+ initialState: {
+ pagination,
+ columnVisibility: {
+ created_at: false,
+ created_by: false
+ }
+ },
+ manualSorting: true,
+ onSortingChange: customSorting,
+ state: {
+ sorting,
+ pagination
+ },
+ })
+
+ return (
+ <>
+
+
+
+ {t("collection")}
+
+
+
+
+ {t("create")}
+
+
+
+
+
+
+
+ >
+ )
+})
\ No newline at end of file
diff --git a/react-admin/src/pages/collection/hooks/useCollectionTable.ts b/react-admin/src/pages/collection/hooks/useCollectionTable.ts
new file mode 100644
index 00000000..be64ae28
--- /dev/null
+++ b/react-admin/src/pages/collection/hooks/useCollectionTable.ts
@@ -0,0 +1,35 @@
+import {useQuery} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+import PaginateType from "../../../types/misc/PaginateType";
+
+export const useCollectionTable = (query: PaginateType) => {
+ let params: URLSearchParams = new URLSearchParams();
+ if (query.page && query.page > 0) {
+ params.append("page", query.page.toString())
+ }
+ if (query.order && query.order !== "") {
+ params.append("order", query.order)
+ }
+ let query_string = "";
+ if (params.toString() !== "") {
+ query_string = "?" + params.toString()
+ }
+
+ const client = useAxios();
+ const redirect = useNavigate();
+ return useQuery({
+ queryKey: ['collection-table', query],
+ queryFn: (async () => {
+ try {
+ return await client.get("/collection" + query_string)
+ } catch (error) {
+ if (_.get(error, 'response.status') === 401) {
+ localStorage.removeItem('AUTH_TOKEN')
+ redirect("/admin/login")
+ }
+ }
+ })
+ })
+}
\ No newline at end of file
diff --git a/react-admin/src/pages/collection/hooks/useGetCollection.ts b/react-admin/src/pages/collection/hooks/useGetCollection.ts
new file mode 100644
index 00000000..0945e131
--- /dev/null
+++ b/react-admin/src/pages/collection/hooks/useGetCollection.ts
@@ -0,0 +1,23 @@
+import {useQuery} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+
+export const useGetCollection = (collection_id: string) => {
+ const client = useAxios()
+ const redirect = useNavigate()
+
+ return useQuery({
+ queryKey: ['collection', collection_id],
+ queryFn: (async () => {
+ try {
+ return await client.get("/collection/" + collection_id)
+ } catch (error) {
+ if (_.get(error, 'response.status') === 401) {
+ localStorage.removeItem('AUTH_TOKEN')
+ redirect("/admin/login")
+ }
+ }
+ })
+ })
+}
\ No newline at end of file
diff --git a/react-admin/src/pages/collection/hooks/usePutCollectionIdentifier.ts b/react-admin/src/pages/collection/hooks/usePutCollectionIdentifier.ts
new file mode 100644
index 00000000..a7a6bf87
--- /dev/null
+++ b/react-admin/src/pages/collection/hooks/usePutCollectionIdentifier.ts
@@ -0,0 +1,21 @@
+import {useMutation} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+import {PutCollectionIdentifierType} from "../../../types/collection/PutCollectionIdentifierType";
+
+export const usePutCollectionIdentifier = (collection_id: string) => {
+ const client = useAxios();
+ const redirect = useNavigate();
+ return useMutation({
+ mutationFn: async (data: PutCollectionIdentifierType) => {
+ const url = '/put-collection-identifier/' + collection_id;
+ return await client.put(url , JSON.stringify(data));
+ },
+ onSuccess: (res) => {
+ if (_.get(res, 'data.status') === true) {
+ redirect("/admin/collection-edit/" + collection_id)
+ }
+ }
+ })
+}
\ No newline at end of file
diff --git a/react-admin/src/pages/collection/hooks/useStoreCollection.ts b/react-admin/src/pages/collection/hooks/useStoreCollection.ts
new file mode 100644
index 00000000..607e851e
--- /dev/null
+++ b/react-admin/src/pages/collection/hooks/useStoreCollection.ts
@@ -0,0 +1,20 @@
+import {useMutation} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+import {SavableCollectionType} from "../../../types/collection/CreatableCollectionType";
+
+export const useStoreCollection = () => {
+ const client = useAxios();
+ const redirect = useNavigate();
+ return useMutation({
+ mutationFn: async (data: SavableCollectionType) => {
+ return await client.post('/collection', JSON.stringify(data));
+ },
+ onSuccess: (res) => {
+ if (_.get(res, 'data.status') === true) {
+ redirect("/admin/collections")
+ }
+ }
+ })
+}
\ No newline at end of file
diff --git a/react-admin/src/pages/collection/hooks/useUpdateCollection.ts b/react-admin/src/pages/collection/hooks/useUpdateCollection.ts
new file mode 100644
index 00000000..3abb6cdf
--- /dev/null
+++ b/react-admin/src/pages/collection/hooks/useUpdateCollection.ts
@@ -0,0 +1,21 @@
+import {useMutation} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+import IEditableModel from "../../../types/model/IEditableModel";
+
+export const useUpdateCollection = (role_id: string) => {
+ const client = useAxios();
+ const redirect = useNavigate();
+ return useMutation({
+ mutationFn: async (data: IEditableModel) => {
+ const url = '/collection/' + role_id;
+ return await client.put(url , JSON.stringify(data));
+ },
+ onSuccess: (res) => {
+ if (_.get(res, 'data.status') === true) {
+ redirect("/admin/collection")
+ }
+ }
+ })
+}
\ No newline at end of file
diff --git a/react-admin/src/pages/collection/schemas/CollectionCreateSchema.ts b/react-admin/src/pages/collection/schemas/CollectionCreateSchema.ts
new file mode 100644
index 00000000..2eb1ea8e
--- /dev/null
+++ b/react-admin/src/pages/collection/schemas/CollectionCreateSchema.ts
@@ -0,0 +1,15 @@
+import Joi from 'joi';
+import {useTranslation} from "react-i18next";
+
+export const useCollectionCreateSchema = (() => {
+
+ const [t] = useTranslation("global")
+ return Joi.object({
+ name : Joi.string().required().messages({
+ 'string.empty': t("empty_message", {attribute: t("name")}),
+ }),
+ identifier : Joi.string().required().messages({
+ 'string.empty': t("empty_message", {attribute: t("identifier")}),
+ })
+ });
+})
diff --git a/react-admin/src/pages/collection/schemas/CollectionEditSchema.ts b/react-admin/src/pages/collection/schemas/CollectionEditSchema.ts
new file mode 100644
index 00000000..765e1f34
--- /dev/null
+++ b/react-admin/src/pages/collection/schemas/CollectionEditSchema.ts
@@ -0,0 +1,12 @@
+import Joi from 'joi';
+import {useTranslation} from "react-i18next";
+
+export const useCollectionEditSchema = (() => {
+
+ const [t] = useTranslation("global")
+ return Joi.object({
+ name : Joi.string().required().messages({
+ 'string.empty': t("empty_message", {attribute: t("name")}),
+ })
+ });
+})
diff --git a/react-admin/src/pages/collection/schemas/CollectionPutSchema.ts b/react-admin/src/pages/collection/schemas/CollectionPutSchema.ts
new file mode 100644
index 00000000..b19d9406
--- /dev/null
+++ b/react-admin/src/pages/collection/schemas/CollectionPutSchema.ts
@@ -0,0 +1,12 @@
+import Joi from 'joi';
+import {useTranslation} from "react-i18next";
+
+export const useCollectionPutSchema = (() => {
+
+ const [t] = useTranslation("global")
+ return Joi.object({
+ identifier : Joi.string().required().messages({
+ 'string.empty': t("empty_message", {attribute: t("identifier")}),
+ })
+ });
+})
diff --git a/react-admin/src/pages/content/ContentCreate.tsx b/react-admin/src/pages/content/ContentCreate.tsx
new file mode 100644
index 00000000..72357637
--- /dev/null
+++ b/react-admin/src/pages/content/ContentCreate.tsx
@@ -0,0 +1,249 @@
+import {useTranslation} from "react-i18next";
+import {ContentSidebar} from "./ContentSidebar";
+import {Link, useSearchParams} from "react-router-dom";
+import InputField from "../../components/InputField";
+import ErrorMessage from "../../components/ErrorMessage";
+import React, { useState } from "react";
+import {
+ AvoRedContentDataType,
+ AvoRedContentFieldType,
+ SaveContentFieldType,
+ SaveContentType
+} from "../../types/content/ContentType";
+import slug from "slug";
+import {Controller, useFieldArray, useForm} from "react-hook-form";
+import {joiResolver} from "@hookform/resolvers/joi";
+import {useContentCreateSchema} from "./schemas/useContentCreateSchema";
+import {useStoreContent} from "./hooks/useStoreContent";
+import AvoRedButton, { ButtonType } from "../../components/AvoRedButton";
+import _ from 'lodash';
+import { ContentFieldModal } from "./ContentFieldModal";
+import {Cog8ToothIcon, TrashIcon} from "@heroicons/react/24/solid";
+
+export const ContentCreate = (() => {
+ const [t] = useTranslation("global")
+ const [isContentFieldModalOpen, setIsContentFieldModalOpen] = useState(false);
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const [searchParams] = useSearchParams()
+ const collectionType: string = searchParams.get("type") as string
+ const {mutate, error} = useStoreContent()
+
+ const submitHandler = (async (data: SaveContentType) => {
+ mutate(data)
+ })
+
+ const {
+ register,
+ handleSubmit,
+ setValue,
+ getValues,
+ formState: {errors},
+ control,
+ trigger
+ } = useForm({
+ resolver: joiResolver(useContentCreateSchema(), {allowUnknown: true})
+ })
+
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: "content_fields", //rename fields
+ });
+
+ const deleteContentFieldOnClick = (e: any, index: number) => {
+ e.preventDefault();
+ remove(index);
+ setCurrentIndex(0);
+ };
+
+ const onNameChange = (e: React.KeyboardEvent) => {
+ setValue('identifier', slug(e.currentTarget.value || ''))
+ }
+
+ const addFieldButtonOnClick = (async (e: React.MouseEvent, max_index: number) => {
+ e.preventDefault()
+ append({
+ name: '',
+ identifier: '',
+ data_type: AvoRedContentDataType.TEXT,
+ field_type: AvoRedContentFieldType.TEXT,
+ field_content: {
+ text_value: {
+ text_value: ""
+ }
+ }
+ })
+ await trigger("content_fields");
+ setCurrentIndex(max_index);
+ setIsContentFieldModalOpen(true)
+ })
+
+ const renderField = (field: SaveContentFieldType, index: number) => {
+ switch (field.field_type) {
+ case AvoRedContentFieldType.TEXT:
+ return (
+
+
+
+ );
+ }
+
+ }
+
+ return (
+
+
+ )
+})
diff --git a/react-admin/src/pages/content/ContentEdit.tsx b/react-admin/src/pages/content/ContentEdit.tsx
new file mode 100644
index 00000000..48de8178
--- /dev/null
+++ b/react-admin/src/pages/content/ContentEdit.tsx
@@ -0,0 +1,288 @@
+import {useTranslation} from "react-i18next"
+import {ContentSidebar} from "./ContentSidebar"
+import {Link, useParams, useSearchParams} from "react-router-dom"
+import InputField from "../../components/InputField"
+import ErrorMessage from "../../components/ErrorMessage"
+import React, { useState } from "react"
+import {
+ AvoRedContentDataType,
+ AvoRedContentFieldType,
+ SaveContentFieldType,
+ SaveContentType
+} from "../../types/content/ContentType"
+import {Controller, useFieldArray, useForm} from "react-hook-form"
+import {joiResolver} from "@hookform/resolvers/joi"
+import AvoRedButton, { ButtonType } from "../../components/AvoRedButton"
+import _ from 'lodash'
+import { ContentFieldModal } from "./ContentFieldModal"
+import {Cog8ToothIcon, TrashIcon} from "@heroicons/react/24/solid"
+import {useContentEditSchema} from "./schemas/useContentEditSchema"
+import {useGetContent} from "./hooks/useGetContent";
+import {useUpdateContent} from "./hooks/useUpdateContent";
+import {usePutContentIdentifier} from "./hooks/usePutContentIdentifier";
+
+export const ContentEdit = (() => {
+ const [t] = useTranslation("global")
+ const [isContentFieldModalOpen, setIsContentFieldModalOpen] = useState(false);
+ const [isEditableIdentifier, setIsEditableIdentifier] = useState(true);
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const [searchParams] = useSearchParams()
+ const collectionType: string = searchParams.get("type") as string
+ const params = useParams()
+
+ const {mutate, error} = useUpdateContent(params.content_id ?? "", collectionType)
+
+ const { data } = useGetContent(params.content_id ?? "", collectionType)
+ const values = data?.data.data
+
+ const submitHandler = (async (data: SaveContentType) => {
+ mutate(data)
+ })
+
+ const {
+ register,
+ handleSubmit,
+ setValue,
+ getValues,
+ formState: {errors},
+ control,
+ trigger
+ } = useForm({
+ resolver: joiResolver(useContentEditSchema(), {allowUnknown: true}),
+ values
+ })
+
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: "content_fields", //rename fields
+ });
+
+ const { mutate: putContentIdentifierMutate } = usePutContentIdentifier(
+ params.content_id ?? "", collectionType
+ );
+
+
+ const editableIdentifierOnClick = () => {
+ setIsEditableIdentifier(false);
+ };
+ const saveIdentifierOnClick = () => {
+ putContentIdentifierMutate({
+ identifier: getValues("identifier"),
+ });
+ setIsEditableIdentifier(true);
+ };
+
+ const cancelIdentifierOnClick = () => {
+ setIsEditableIdentifier(true);
+ };
+
+ const deleteContentFieldOnClick = (e: any, index: number) => {
+ e.preventDefault();
+ remove(index);
+ setCurrentIndex(0);
+ };
+
+ const addFieldButtonOnClick = (async (e: React.MouseEvent, max_index: number) => {
+ e.preventDefault()
+ append({
+ name: '',
+ identifier: '',
+ data_type: AvoRedContentDataType.TEXT,
+ field_type: AvoRedContentFieldType.TEXT,
+ field_content: {
+ text_value: {
+ text_value: ""
+ }
+ }
+ })
+ await trigger("content_fields");
+ setCurrentIndex(max_index);
+ setIsContentFieldModalOpen(true)
+ })
+
+ const renderField = (field: SaveContentFieldType, index: number) => {
+ switch (field.field_type) {
+ case AvoRedContentFieldType.TEXT:
+ return (
+
+
+
+ );
+ }
+
+ }
+
+ return (
+
+
+ )
+})
diff --git a/react-admin/src/pages/content/ContentFieldModal.tsx b/react-admin/src/pages/content/ContentFieldModal.tsx
new file mode 100644
index 00000000..2e0f0d19
--- /dev/null
+++ b/react-admin/src/pages/content/ContentFieldModal.tsx
@@ -0,0 +1,128 @@
+import {
+ UseFormGetValues,
+ UseFormRegister,
+ UseFormSetValue,
+ UseFormTrigger,
+} from "react-hook-form";
+import {
+ AvoRedContentDataType,
+ AvoRedContentFieldType,
+ SaveContentType,
+} from "../../types/content/ContentType";
+import AvoredModal from "../../components/AvoredModal";
+import AvoRedButton from "../../components/AvoRedButton";
+import { useTranslation } from "react-i18next";
+import InputField from "../../components/InputField";
+import React from "react";
+import slug from "slug";
+
+type ContentFieldProps = {
+ register: UseFormRegister;
+ currentIndex: number;
+ getValues: UseFormGetValues;
+ setValue: UseFormSetValue;
+ trigger: UseFormTrigger;
+ setIsOpen: React.Dispatch>;
+ isOpen: boolean;
+ collectionType: string;
+};
+
+export const ContentFieldModal = ({
+ register,
+ currentIndex,
+ getValues,
+ setValue,
+ trigger,
+ setIsOpen,
+ isOpen,
+ collectionType,
+}: ContentFieldProps) => {
+ const [t] = useTranslation("global");
+
+ const onContentFieldChange = async (
+ index: number,
+ field_type: AvoRedContentFieldType,
+ data_type: AvoRedContentDataType
+ ) => {
+ setValue(`content_fields.${index}.field_type`, field_type);
+ setValue(`content_fields.${index}.data_type`, data_type);
+ await trigger(`content_fields.${index}`);
+ };
+
+ const contentFieldNameInputChange = ((e: React.KeyboardEvent, index: number) => {
+ e.stopPropagation();
+
+ setValue(`content_fields.${index}.identifier`, slug(e.currentTarget.value));
+ })
+
+ return (
+ setIsOpen(false)}
+ modal_body={
+
+
+
+
+ ) => contentFieldNameInputChange(e, currentIndex)}
+ register={register(`content_fields.${currentIndex}.name`)}
+ />
+
+
+
+
+
+ {/*
{renderFieldData(currentIndex)}
*/}
+
+
+
+
+
+ onContentFieldChange(
+ currentIndex,
+ AvoRedContentFieldType.TEXT,
+ AvoRedContentDataType.TEXT
+ )
+ }
+ className={`${getValues(`content_fields.${currentIndex}.field_type`) === AvoRedContentFieldType.TEXT ? "bg-primary-200" : "bg-gray-300"}
+ ring-1 ring-gray-300 hover:cursor-pointer hover:ring-primary-300 p-3 rounded`}
+ >
+ {t("text_field")}
+
+
+
+
+
+
+
+
+
setIsOpen(false)}
+ className="bg-primary-500"
+ label={t("create_content_field")}
+ />
+
+
+
setIsOpen(false)}
+ label={t("cancel")}
+ />
+
+
+
+
+ }
+ modal_header={`${t(collectionType)} ${t("content_field")}`}
+ isOpen={isOpen}
+ >
+ );
+};
diff --git a/react-admin/src/pages/content/ContentSidebar.tsx b/react-admin/src/pages/content/ContentSidebar.tsx
new file mode 100644
index 00000000..be8aaf2e
--- /dev/null
+++ b/react-admin/src/pages/content/ContentSidebar.tsx
@@ -0,0 +1,37 @@
+import {Link, useSearchParams} from "react-router-dom";
+import {useTranslation} from "react-i18next";
+import {useCollectionAll} from "./hooks/useCollectionAll";
+import {CollectionType} from "../../types/collection/CollectionType";
+import _ from 'lodash';
+
+export const ContentSidebar = (() => {
+ const [t] = useTranslation("global");
+ const [searchParams] = useSearchParams()
+ const collections_api_response = useCollectionAll()
+ const collections: Array = _.get(collections_api_response, 'data.data.data', [])
+ return (
+ <>
+
+ {t("collections")}
+
+
+
+ {collections.map((collection: CollectionType) => {
+ return (
+ -
+
+ {collection.name}
+
+
+ )
+ })}
+
+
+
+ >
+ )
+})
\ No newline at end of file
diff --git a/react-admin/src/pages/content/ContentTable.tsx b/react-admin/src/pages/content/ContentTable.tsx
new file mode 100644
index 00000000..aeba42b0
--- /dev/null
+++ b/react-admin/src/pages/content/ContentTable.tsx
@@ -0,0 +1,167 @@
+import {useTranslation} from "react-i18next";
+import {ContentSidebar} from "./ContentSidebar";
+import {Link, useSearchParams} from "react-router-dom";
+import HasPermission from "../../components/HasPermission";
+import _ from 'lodash';
+import {ContentModel} from "../../types/content/ContentType";
+import {useContentTable} from "./hooks/useContentTable";
+import {useState} from "react";
+import {createColumnHelper, getCoreRowModel, SortingState, useReactTable} from "@tanstack/react-table";
+import {getFormattedDate} from "../../lib/common";
+import AvoRedTable from "../../components/AvoRedTable";
+
+export const ContentTable = (() => {
+ const [t] = useTranslation("global");
+ const [searchParams] = useSearchParams()
+ const collectionType = searchParams.get("type")
+
+ const [pagination, setPagination] = useState({
+ pageIndex: 0, //initial page index
+ pageSize: 10, //default page size
+ });
+ const [sorting, setSorting] = useState([]);
+ const content_api_table_response = useContentTable({
+ order: sorting.map((s) => `${s.id}:${s.desc ? "DESC" : "ASC"}`).join(","),
+ page: pagination.pageIndex
+ }, collectionType);
+
+ const contents: Array = _.get(
+ content_api_table_response,
+ "data.data.data.data",
+ [],
+ );
+
+
+ const customSorting = (sorting: any) => {
+ setSorting(sorting);
+ };
+
+ const customPagination = (async (pagination: any) => {
+ setPagination(pagination)
+ })
+
+ const columnHelper = createColumnHelper();
+ const columns = [
+ columnHelper.accessor("id", {
+ cell: (info) => info.getValue(),
+ header: t("id"),
+ }),
+ columnHelper.accessor("name", {
+ cell: (info) => info.getValue(),
+ header: t("name"),
+ }),
+ columnHelper.accessor("identifier", {
+ cell: (info) => info.getValue(),
+ header: t("identifier"),
+ }),
+ columnHelper.accessor("created_at", {
+ id: "created_at",
+ cell: (info) => getFormattedDate(info.getValue()),
+ header: t("created_at"),
+ }),
+ columnHelper.accessor("created_by", {
+ cell: (info) => info.getValue(),
+ header: t("created_by"),
+ }),
+ columnHelper.accessor("updated_at", {
+ cell: (info) => getFormattedDate(info.getValue()),
+ header: t("updated_at"),
+ }),
+ columnHelper.accessor("updated_by", {
+ cell: (info) => info.getValue(),
+ header: t("updated_by"),
+ }),
+ columnHelper.accessor("action", {
+ cell: (info) => {
+ return (
+
+
+ {t("edit")}
+
+ {/**/}
+ {/* onDeleteSelect(e, info.row.original)}*/}
+ {/* >*/}
+ {/* {t("delete")}*/}
+ {/* */}
+ {/*
*/}
+
+ );
+ },
+ enableSorting: false,
+ header: t("action"),
+ enableHiding: false,
+ }),
+ ];
+
+
+ const table = useReactTable({
+ data: contents,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ rowCount: _.get(content_api_table_response, '.data.data.data.pagination.total', 10),
+ onPaginationChange: customPagination,
+ manualPagination: true,
+ initialState: {
+ columnVisibility: {
+ created_at: false,
+ created_by: false,
+ },
+ pagination
+ },
+ manualSorting: true,
+ onSortingChange: customSorting,
+ state: {
+ sorting,
+ pagination
+ },
+ });
+
+
+ return (
+
+
+
+
+
+ {collectionType ?
+ <>
+
+
+ {t("collection type table title")}
+
+
+
+
+ {t("create")}
+
+
+
+
+
+ >
+
+ :
+ <>
+
+ {t("please select the type on left hand side")}
+
+ >
+ }
+
+
+
+ )
+})
\ No newline at end of file
diff --git a/react-admin/src/pages/content/hooks/useCollectionAll.ts b/react-admin/src/pages/content/hooks/useCollectionAll.ts
new file mode 100644
index 00000000..93149d23
--- /dev/null
+++ b/react-admin/src/pages/content/hooks/useCollectionAll.ts
@@ -0,0 +1,25 @@
+import {useQuery} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+
+export const useCollectionAll = () => {
+
+ const client = useAxios();
+ const redirect = useNavigate();
+ return useQuery({
+ queryKey: ['collection-all'],
+ queryFn: (async () => {
+ try {
+ return await client.get("/collection-all")
+ } catch (error) {
+ if (_.get(error, 'response.status') === 401) {
+ localStorage.removeItem('AUTH_TOKEN')
+ redirect("/admin/login")
+ }
+
+ console.error(error)
+ }
+ })
+ })
+}
\ No newline at end of file
diff --git a/react-admin/src/pages/content/hooks/useContentTable.ts b/react-admin/src/pages/content/hooks/useContentTable.ts
new file mode 100644
index 00000000..0af8c61f
--- /dev/null
+++ b/react-admin/src/pages/content/hooks/useContentTable.ts
@@ -0,0 +1,41 @@
+import {useQuery} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+import PaginateType from "../../../types/misc/PaginateType";
+
+export const useContentTable = (query: PaginateType, collectionType: string | null) => {
+ let params: URLSearchParams = new URLSearchParams();
+ if (query.page && query.page > 0) {
+ params.append("page", query.page.toString())
+ }
+ if (query.order && query.order !== "") {
+ params.append("order", query.order)
+ }
+ let query_string = "";
+ if (params.toString() !== "") {
+ query_string = "?" + params.toString()
+ }
+
+ const client = useAxios();
+ const redirect = useNavigate();
+ return useQuery({
+ queryKey: ['content-table', query, collectionType],
+ queryFn: (async () => {
+ // early exit
+ if (_.isEmpty(collectionType)) {
+ return { data: undefined }
+ }
+
+ try {
+
+ return await client.get(`/content/${collectionType?.trim()}${query_string}`)
+ } catch (error) {
+ if (_.get(error, 'response.status') === 401) {
+ localStorage.removeItem('AUTH_TOKEN')
+ redirect("/admin/login")
+ }
+ }
+ })
+ })
+}
diff --git a/react-admin/src/pages/content/hooks/useGetContent.ts b/react-admin/src/pages/content/hooks/useGetContent.ts
new file mode 100644
index 00000000..bf521398
--- /dev/null
+++ b/react-admin/src/pages/content/hooks/useGetContent.ts
@@ -0,0 +1,23 @@
+import {useQuery} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+
+export const useGetContent = (content_id: string, collection_type: string) => {
+ const client = useAxios()
+ const redirect = useNavigate()
+
+ return useQuery({
+ queryKey: ['content', content_id],
+ queryFn: (async () => {
+ try {
+ return await client.get(`/content/${collection_type}/${content_id}`)
+ } catch (error) {
+ if (_.get(error, 'response.status') === 401) {
+ localStorage.removeItem('AUTH_TOKEN')
+ redirect("/admin/login")
+ }
+ }
+ })
+ })
+}
\ No newline at end of file
diff --git a/react-admin/src/pages/content/hooks/usePutContentIdentifier.ts b/react-admin/src/pages/content/hooks/usePutContentIdentifier.ts
new file mode 100644
index 00000000..b802b93c
--- /dev/null
+++ b/react-admin/src/pages/content/hooks/usePutContentIdentifier.ts
@@ -0,0 +1,21 @@
+import {useMutation} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+import {PutRoleIdentifierType} from "../../../types/role/PutRoleIdentifierType";
+
+export const usePutContentIdentifier = (content_id: string, collectionType: string) => {
+ const client = useAxios();
+ const redirect = useNavigate();
+ return useMutation({
+ mutationFn: async (data: PutRoleIdentifierType) => {
+ const url = `/put-content-identifier/${collectionType}/${content_id}`;
+ return await client.put(url , JSON.stringify(data));
+ },
+ onSuccess: (res) => {
+ if (_.get(res, 'data.status') === true) {
+ redirect("/admin/content-edit/" + content_id + "?type=" + collectionType)
+ }
+ }
+ })
+}
\ No newline at end of file
diff --git a/react-admin/src/pages/content/hooks/useStoreContent.ts b/react-admin/src/pages/content/hooks/useStoreContent.ts
new file mode 100644
index 00000000..2541a80e
--- /dev/null
+++ b/react-admin/src/pages/content/hooks/useStoreContent.ts
@@ -0,0 +1,21 @@
+import {useMutation} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+import {SaveContentType} from "../../../types/content/ContentType";
+
+export const useStoreContent = () => {
+ const client = useAxios();
+ const redirect = useNavigate();
+ return useMutation({
+ mutationFn: async (data: SaveContentType) => {
+ const url = `/content`;
+ return await client.post(url , JSON.stringify(data));
+ },
+ onSuccess: (res) => {
+ if (_.get(res, 'data.status') === true) {
+ redirect("/admin/content")
+ }
+ }
+ })
+}
\ No newline at end of file
diff --git a/react-admin/src/pages/content/hooks/useUpdateContent.ts b/react-admin/src/pages/content/hooks/useUpdateContent.ts
new file mode 100644
index 00000000..f7529075
--- /dev/null
+++ b/react-admin/src/pages/content/hooks/useUpdateContent.ts
@@ -0,0 +1,21 @@
+import {useMutation} from '@tanstack/react-query'
+import { useAxios } from '../../../hooks/useAxios'
+import _ from 'lodash'
+import {useNavigate} from 'react-router-dom'
+import {SaveContentType} from "../../../types/content/ContentType"
+
+export const useUpdateContent = (content_id: string, collection_type: string) => {
+ const client = useAxios();
+ const redirect = useNavigate();
+ return useMutation({
+ mutationFn: async (data: SaveContentType) => {
+ const url = `/content/${collection_type}/${content_id}`;
+ return await client.put(url , JSON.stringify(data));
+ },
+ onSuccess: (res) => {
+ if (_.get(res, 'data.status') === true) {
+ redirect("/admin/content?type=" + collection_type)
+ }
+ }
+ })
+}
\ No newline at end of file
diff --git a/react-admin/src/pages/content/schemas/useContentCreateSchema.ts b/react-admin/src/pages/content/schemas/useContentCreateSchema.ts
new file mode 100644
index 00000000..16a0caba
--- /dev/null
+++ b/react-admin/src/pages/content/schemas/useContentCreateSchema.ts
@@ -0,0 +1,15 @@
+import Joi from 'joi';
+import {useTranslation} from "react-i18next";
+
+export const useContentCreateSchema = (() => {
+
+ const [t] = useTranslation("global")
+ return Joi.object({
+ name : Joi.string().required().messages({
+ 'string.empty': t("empty_message", {attribute: t("name")}),
+ }),
+ identifier : Joi.string().required().messages({
+ 'string.empty': t("empty_message", {attribute: t("identifier")}),
+ })
+ });
+})
diff --git a/react-admin/src/pages/content/schemas/useContentEditSchema.ts b/react-admin/src/pages/content/schemas/useContentEditSchema.ts
new file mode 100644
index 00000000..f346ff57
--- /dev/null
+++ b/react-admin/src/pages/content/schemas/useContentEditSchema.ts
@@ -0,0 +1,14 @@
+import Joi from 'joi';
+import {useTranslation} from "react-i18next";
+
+export const useContentEditSchema = (() => {
+ const [t] = useTranslation("global")
+ return Joi.object({
+ name : Joi.string().required().messages({
+ 'string.empty': t("empty_message", {attribute: t("name")}),
+ }),
+ identifier : Joi.string().required().messages({
+ 'string.empty': t("empty_message", {attribute: t("identifier")}),
+ }),
+ })
+})
\ No newline at end of file
diff --git a/react-admin/src/pages/models/ModelCreatePage.tsx b/react-admin/src/pages/models/ModelCreatePage.tsx
index 4c41ac19..0bbd590c 100644
--- a/react-admin/src/pages/models/ModelCreatePage.tsx
+++ b/react-admin/src/pages/models/ModelCreatePage.tsx
@@ -1,24 +1,40 @@
import {Link} from "react-router-dom";
import InputField from "../../components/InputField";
import {useTranslation} from "react-i18next";
-import {useForm} from "react-hook-form";
+import {Controller, useFieldArray, useForm} from "react-hook-form";
import {joiResolver} from "@hookform/resolvers/joi";
import ErrorMessage from "../../components/ErrorMessage";
import {CreatableModelType} from "../../types/model/CreatableModelType";
import {useStoreModel} from "./hooks/useStoreModel";
import {useModelCreateSchema} from "./schemas/ModelCreateSchema";
+import {ModelSidebar} from "./ModelSidebar";
export const ModelCreatePage = () => {
const [t] = useTranslation("global")
const {mutate, error} = useStoreModel()
const {
+ control,
register,
handleSubmit,
formState: {errors},
+ trigger,
} = useForm({
resolver: joiResolver(useModelCreateSchema(), {allowUnknown: true}),
})
+ const { fields, append, remove } = useFieldArray({
+ control,
+ name: "model_fields", //rename fields
+ });
+
+ const addModelAddOnClick = (async (e: React.MouseEvent) => {
+ e.preventDefault()
+ append({
+ name: ""
+ })
+ await trigger("model_fields")
+ })
+
const submitHandler = ((data: CreatableModelType) => {
mutate(data)
})
@@ -30,50 +46,83 @@ export const ModelCreatePage = () => {
{t("model_information")}
-
-