From 1a6ead47607b024d79fb1a9da1cf0366426a1128 Mon Sep 17 00:00:00 2001 From: Dennis Brentjes Date: Fri, 27 May 2022 20:53:12 +0200 Subject: [PATCH] Adds the ability to add games with suggestions from known games. --- backend/Cargo.lock | 42 +- backend/Cargo.toml | 8 +- .../2022-03-19-191822_initial/up.sql | 10 +- .../2022-04-17-175115_user-system/down.sql | 2 +- .../2022-04-17-175115_user-system/up.sql | 31 +- .../2022-05-14-104118_gamenight_owners/up.sql | 18 +- .../down.sql | 3 + .../up.sql | 9 + backend/src/api.rs | 95 +- backend/src/main.rs | 10 +- backend/src/schema.rs | 212 +++-- docker-compose.yml | 23 + frontend/package-lock.json | 816 ++++++++++++++++++ frontend/package.json | 12 +- frontend/src/App.js | 24 +- frontend/src/components/AddGameNight.jsx | 29 +- frontend/src/components/GameAdder.jsx | 60 ++ frontend/src/components/Gamenights.jsx | 4 +- 18 files changed, 1240 insertions(+), 168 deletions(-) create mode 100644 backend/migrations/2022-05-27-183310_gamenight_gamelist/down.sql create mode 100644 backend/migrations/2022-05-27-183310_gamenight_gamelist/up.sql create mode 100644 docker-compose.yml create mode 100644 frontend/src/components/GameAdder.jsx diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 8dc2590..f456cc9 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -342,10 +342,12 @@ version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b28135ecf6b7d446b43e27e225622a038cc4e2930a1022f51cdb97ada19b8e4d" dependencies = [ + "bitflags", "byteorder", "diesel_derives", - "libsqlite3-sys", + "pq-sys", "r2d2", + "uuid", ] [[package]] @@ -583,7 +585,6 @@ dependencies = [ "diesel-derive-enum", "diesel_migrations", "jsonwebtoken", - "libsqlite3-sys", "local-ip-address", "password-hash", "rand_core", @@ -592,6 +593,7 @@ dependencies = [ "rocket_dyn_templates", "rocket_sync_db_pools", "serde", + "uuid", "validator", ] @@ -913,17 +915,6 @@ version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" -[[package]] -name = "libsqlite3-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e704a02bcaecd4a08b93a23f6be59d0bd79cd161e0963e9499165a0a35df7bd" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "local-ip-address" version = "0.4.4" @@ -1392,12 +1383,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkg-config" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" - [[package]] name = "polyval" version = "0.5.3" @@ -1416,6 +1401,15 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +[[package]] +name = "pq-sys" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda" +dependencies = [ + "vcpkg", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -2274,6 +2268,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" +dependencies = [ + "getrandom", + "serde", +] + [[package]] name = "validator" version = "0.14.0" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 8c606ab..ca9cf16 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -8,9 +8,8 @@ edition = "2018" [dependencies] rocket = { version = "0.5.0-rc.2", features = ["default", "json"] } -libsqlite3-sys = { version = ">=0.8.0, <0.19.0", features = ["bundled"] } -rocket_sync_db_pools = { version = "0.1.0-rc.2", features = ["diesel_sqlite_pool"] } -diesel = { version = "1.4.8", features = ["sqlite"] } +rocket_sync_db_pools = { version = "0.1.0-rc.2", features = ["diesel_postgres_pool"] } +diesel = {version = "1.4.8", features = ["uuidv07", "r2d2", "postgres"]} diesel_migrations = "1.4.0" rocket_dyn_templates = { version = "0.1.0-rc.2", features = ["handlebars"] } chrono = "0.4.19" @@ -18,8 +17,9 @@ serde = "1.0.136" password-hash = "0.4" argon2 = "0.4" rand_core = { version = "0.6", features = ["std"] } -diesel-derive-enum = { version = "1.1", features = ["sqlite"] } +diesel-derive-enum = { version = "1.1", features = ["postgres"] } jsonwebtoken = "8.1" validator = { version = "0.14", features = ["derive"] } rocket_cors = "0.6.0-alpha1" local-ip-address = "0.4" +uuid = { version = "0.8.2", features = ["v4", "serde"] } \ No newline at end of file diff --git a/backend/migrations/2022-03-19-191822_initial/up.sql b/backend/migrations/2022-03-19-191822_initial/up.sql index bf15d47..3513943 100644 --- a/backend/migrations/2022-03-19-191822_initial/up.sql +++ b/backend/migrations/2022-03-19-191822_initial/up.sql @@ -1,12 +1,12 @@ -- Your SQL goes here CREATE TABLE gamenight ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - game text TEXT NOT NULL, - datetime TEXT NOT NULL + id UUID NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + datetime VARCHAR NOT NULL ); CREATE TABLE known_games ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - game TEXT UNIQUE NOT NULL + id UUID NOT NULL PRIMARY KEY, + name VARCHAR UNIQUE NOT NULL ); \ No newline at end of file diff --git a/backend/migrations/2022-04-17-175115_user-system/down.sql b/backend/migrations/2022-04-17-175115_user-system/down.sql index 78614ae..bc7f547 100644 --- a/backend/migrations/2022-04-17-175115_user-system/down.sql +++ b/backend/migrations/2022-04-17-175115_user-system/down.sql @@ -1,4 +1,4 @@ -- This file should undo anything in `up.sql` drop table pwd; -drop table user; \ No newline at end of file +drop table users; \ No newline at end of file diff --git a/backend/migrations/2022-04-17-175115_user-system/up.sql b/backend/migrations/2022-04-17-175115_user-system/up.sql index 44cbd3b..710f6f7 100644 --- a/backend/migrations/2022-04-17-175115_user-system/up.sql +++ b/backend/migrations/2022-04-17-175115_user-system/up.sql @@ -1,19 +1,26 @@ -CREATE TABLE user ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - username TEXT UNIQUE NOT NULL, - email TEXT UNIQUE NOT NULL, - role TEXT NOT NULL +CREATE TABLE users ( + id UUID NOT NULL PRIMARY KEY, + username VARCHAR UNIQUE NOT NULL, + email VARCHAR UNIQUE NOT NULL, + role VARCHAR NOT NULL ); CREATE TABLE pwd ( - user_id INTEGER NOT NULL PRIMARY KEY, - password TEXT NOT NULL, - CONSTRAINT FK_UserId FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE + user_id UUID NOT NULL PRIMARY KEY, + password VARCHAR NOT NULL, + CONSTRAINT FK_UserId FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); --Initialize default admin user, with password "gamenight!" -INSERT INTO user (id, username, role) -values(-1, 'admin', 'admin'); +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +DO $$ +DECLARE + admin_uuid uuid = uuid_generate_v4(); +BEGIN + INSERT INTO users (id, username, email, role) + values(admin_uuid, 'admin', '', 'admin'); + + insert INTO pwd (user_id, password) + values(admin_uuid, '$argon2id$v=19$m=4096,t=3,p=1$zEdUjCAnZqd8DziYWzlFHw$YBLQhKvYIZBY43B8zM6hyBvLKuqTeh0EM5pKOfbWQSI'); +END $$; -insert INTO pwd (id, pwd) -values(-1, '$argon2id$v=19$m=4096,t=3,p=1$zEdUjCAnZqd8DziYWzlFHw$YBLQhKvYIZBY43B8zM6hyBvLKuqTeh0EM5pKOfbWQSI'); \ No newline at end of file diff --git a/backend/migrations/2022-05-14-104118_gamenight_owners/up.sql b/backend/migrations/2022-05-14-104118_gamenight_owners/up.sql index eb98702..76b28ff 100644 --- a/backend/migrations/2022-05-14-104118_gamenight_owners/up.sql +++ b/backend/migrations/2022-05-14-104118_gamenight_owners/up.sql @@ -1,19 +1,19 @@ ALTER TABLE gamenight RENAME TO _gamenight_old; CREATE TABLE gamenight ( - id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - game text TEXT NOT NULL, - datetime TEXT NOT NULL, - owner_id INTEGER NOT NULL, - CONSTRAINT FK_OwnerId FOREIGN KEY (owner_id) REFERENCES user(id) ON DELETE CASCADE + id UUID NOT NULL PRIMARY KEY, + name VARCHAR NOT NULL, + datetime VARCHAR NOT NULL, + owner_id UUID NOT NULL, + CONSTRAINT FK_OwnerId FOREIGN KEY (owner_id) REFERENCES users(id) ON DELETE CASCADE ); -PRAGMA foreign_keys=off; +SET session_replication_role = 'replica'; -INSERT INTO gamenight (id, game, datetime, owner_id) -select id, game, datetime, -1 +INSERT INTO gamenight (id, name, datetime, owner_id) +select id, name, datetime, '00000000-0000-0000-0000-000000000000' FROM _gamenight_old; drop table _gamenight_old; -PRAGMA foreign_keys=on; \ No newline at end of file +SET session_replication_role = 'origin'; \ No newline at end of file diff --git a/backend/migrations/2022-05-27-183310_gamenight_gamelist/down.sql b/backend/migrations/2022-05-27-183310_gamenight_gamelist/down.sql new file mode 100644 index 0000000..fbf6a0e --- /dev/null +++ b/backend/migrations/2022-05-27-183310_gamenight_gamelist/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +drop table gamenight_gamelist; \ No newline at end of file diff --git a/backend/migrations/2022-05-27-183310_gamenight_gamelist/up.sql b/backend/migrations/2022-05-27-183310_gamenight_gamelist/up.sql new file mode 100644 index 0000000..153a8f4 --- /dev/null +++ b/backend/migrations/2022-05-27-183310_gamenight_gamelist/up.sql @@ -0,0 +1,9 @@ +-- Your SQL goes here + +create table gamenight_gamelist ( + gamenight_id UUID NOT NULL, + game_id UUID NOT NULL, + CONSTRAINT FK_gamenight_id FOREIGN KEY (gamenight_id) REFERENCES gamenight(id) ON DELETE CASCADE, + CONSTRAINT FK_game_id FOREIGN KEY (game_id) REFERENCES known_games(id) ON DELETE CASCADE, + PRIMARY KEY(gamenight_id, game_id) +); \ No newline at end of file diff --git a/backend/src/api.rs b/backend/src/api.rs index b560611..5f7a959 100644 --- a/backend/src/api.rs +++ b/backend/src/api.rs @@ -1,3 +1,4 @@ +use uuid::Uuid; use crate::schema; use crate::schema::DbConn; use crate::AppConfig; @@ -40,6 +41,8 @@ struct ApiResponse { user: Option, #[serde(skip_serializing_if = "Option::is_none")] gamenights: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + games: Option>, } impl ApiResponse { @@ -51,6 +54,7 @@ impl ApiResponse { message: None, user: None, gamenights: None, + games: None, }; fn error(message: String) -> Self { @@ -59,6 +63,7 @@ impl ApiResponse { message: Some(Cow::Owned(message)), user: None, gamenights: None, + games: None, } } @@ -71,6 +76,7 @@ impl ApiResponse { jwt: jwt }), gamenights: None, + games: None, } } @@ -80,6 +86,17 @@ impl ApiResponse { message: None, user: None, gamenights: Some(gamenights), + games: None, + } + } + + fn games_response(games: Vec) -> Self { + Self { + result: Self::SUCCES_RESULT, + message: None, + user: None, + gamenights: None, + games: Some(games), } } } @@ -143,27 +160,55 @@ pub async fn gamenights_unauthorized() -> ApiResponseVariant { ApiResponseVariant::Status(Status::Unauthorized) } -#[post("/gamenight", format = "application/json", data = "")] -pub async fn gamenight_post_json( - conn: DbConn, - user: schema::User, - gamenight_json: Json, -) -> ApiResponseVariant { - let mut gamenight = gamenight_json.into_inner(); - gamenight.owner_id = Some(user.id); - match schema::insert_gamenight(conn, gamenight).await { - Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), - Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))), +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct GameNightInput { + pub name: String, + pub datetime: String, + pub owner_id: Option, + pub game_list: Vec, +} + +impl Into for GameNightInput { + + fn into(self) -> schema::GameNight { + schema::GameNight { + id: Uuid::new_v4(), + name: self.name, + datetime: self.datetime, + owner_id: self.owner_id.unwrap() + } } } -#[post("/gamenight", rank = 2)] -pub async fn gamenight_post_json_unauthorized() -> ApiResponseVariant { +#[post("/gamenights", format = "application/json", data = "")] +pub async fn gamenights_post_json( + conn: DbConn, + user: schema::User, + gamenight_json: Json, +) -> ApiResponseVariant { + let mut gamenight = gamenight_json.into_inner(); + gamenight.owner_id = Some(user.id); + + let mut mutable_game_list = gamenight.game_list.clone(); + + match schema::add_unknown_games(&conn, &mut mutable_game_list).await { + Ok(_) => (), + Err(err) => return ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) + }; + + match schema::insert_gamenight(conn, gamenight.clone().into(), mutable_game_list).await { + Ok(_) => ApiResponseVariant::Value(json!(ApiResponse::SUCCES)), + Err(err) => ApiResponseVariant::Value(json!(ApiResponse::error(err.to_string()))) + } +} + +#[post("/gamenights", rank = 2)] +pub async fn gamenights_post_json_unauthorized() -> ApiResponseVariant { ApiResponseVariant::Status(Status::Unauthorized) } -#[delete("/gamenight", format = "application/json", data = "")] -pub async fn gamenight_delete_json( +#[delete("/gamenights", format = "application/json", data = "")] +pub async fn gamenights_delete_json( conn: DbConn, user: schema::User, delete_gamenight_json: Json @@ -190,8 +235,8 @@ pub async fn gamenight_delete_json( } -#[delete("/gamenight", rank = 2)] -pub async fn gamenight_delete_json_unauthorized() -> ApiResponseVariant { +#[delete("/gamenights", rank = 2)] +pub async fn gamenights_delete_json_unauthorized() -> ApiResponseVariant { ApiResponseVariant::Status(Status::Unauthorized) } @@ -221,7 +266,7 @@ pub async fn register_post_json( #[derive(Debug, Serialize, Deserialize)] struct Claims { exp: i64, - uid: i32, + uid: Uuid, role: schema::Role, } @@ -261,3 +306,17 @@ pub async fn login_post_json( } } } + +#[get("/games")] +pub async fn games(conn: DbConn, _user: schema::User) -> ApiResponseVariant { + match schema::get_all_known_games(&conn).await { + Ok(games) => ApiResponseVariant::Value(json!(ApiResponse::games_response(games))), + Err(error) => ApiResponseVariant::Value(json!(ApiResponse::error(error.to_string()))) + } +} + +#[get("/games", rank = 2)] +pub async fn games_unauthorized() -> ApiResponseVariant { + ApiResponseVariant::Status(Status::Unauthorized) +} + diff --git a/backend/src/main.rs b/backend/src/main.rs index 70e1bbd..3b4215f 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -58,12 +58,14 @@ async fn rocket() -> _ { routes![ api::gamenights, api::gamenights_unauthorized, - api::gamenight_post_json, - api::gamenight_post_json_unauthorized, + api::gamenights_post_json, + api::gamenights_post_json_unauthorized, api::register_post_json, api::login_post_json, - api::gamenight_delete_json, - api::gamenight_delete_json_unauthorized + api::gamenights_delete_json, + api::gamenights_delete_json_unauthorized, + api::games, + api::games_unauthorized, ], ); diff --git a/backend/src/schema.rs b/backend/src/schema.rs index a8ebe30..2da917d 100644 --- a/backend/src/schema.rs +++ b/backend/src/schema.rs @@ -1,4 +1,4 @@ -use crate::diesel::BoolExpressionMethods; +use uuid::Uuid; use crate::diesel::Connection; use crate::diesel::ExpressionMethods; use crate::diesel::QueryDsl; @@ -9,7 +9,6 @@ use argon2::{ password_hash::{rand_core::OsRng, PasswordHasher}, Argon2, }; -use diesel::dsl::count; use diesel::RunQueryDsl; use diesel_derive_enum::DbEnum; use rocket::{Build, Rocket}; @@ -19,10 +18,10 @@ use std::ops::Deref; use validator::{Validate, ValidationError}; #[database("gamenight_database")] -pub struct DbConn(diesel::SqliteConnection); +pub struct DbConn(diesel::PgConnection); impl Deref for DbConn { - type Target = rocket_sync_db_pools::Connection; + type Target = rocket_sync_db_pools::Connection; fn deref(&self) -> &Self::Target { &self.0 @@ -31,36 +30,40 @@ impl Deref for DbConn { table! { gamenight (id) { - id -> Integer, - game -> Text, - datetime -> Text, - owner_id -> Integer, + id -> diesel::sql_types::Uuid, + name -> VarChar, + datetime -> VarChar, + owner_id -> Uuid, } } table! { - known_games (game) { - id -> Integer, - game -> Text, + known_games (id) { + id -> diesel::sql_types::Uuid, + name -> VarChar, } } table! { - use diesel::sql_types::Integer; - use diesel::sql_types::Text; - use super::RoleMapping; - user(id) { - id -> Integer, - username -> Text, - email -> Text, - role -> RoleMapping, + users(id) { + id -> diesel::sql_types::Uuid, + username -> VarChar, + email -> VarChar, + role -> crate::schema::RoleMapping, } } table! { pwd(user_id) { - user_id -> Integer, - password -> Text, + user_id -> diesel::sql_types::Uuid, + password -> VarChar, + } +} + +table! { + gamenight_gamelist(gamenight_id, game_id) { + gamenight_id -> diesel::sql_types::Uuid, + game_id -> diesel::sql_types::Uuid, } } @@ -98,21 +101,31 @@ pub async fn get_all_gamenights(conn: DbConn) -> Result, Database ).await?) } -pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNightNoId) -> Result { - Ok(conn.run(|c| - diesel::insert_into(gamenight::table) - .values(new_gamenight) - .execute(c) +pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNight, game_list: Vec) -> Result { + Ok(conn.run(move |c| + c.transaction(|| { + diesel::insert_into(gamenight::table) + .values(&new_gamenight) + .execute(c)?; + + let entries: Vec = game_list.iter().map( + |g| GamenightGameListEntry { gamenight_id: new_gamenight.id.clone(), game_id: g.id.clone() } + ).collect(); + + diesel::insert_into(gamenight_gamelist::table) + .values(entries) + .execute(c) + }) ).await?) } -pub async fn get_gamenight(conn: &DbConn, game_id: i32) -> Result { +pub async fn get_gamenight(conn: &DbConn, game_id: Uuid) -> Result { Ok(conn.run(move |c| gamenight::table.find(game_id).first(c) ).await?) } -pub async fn delete_gamenight(conn: &DbConn, game_id: i32) -> Result { +pub async fn delete_gamenight(conn: &DbConn, game_id: Uuid) -> Result { Ok(conn.run(move |c| diesel::delete( gamenight::table.filter( @@ -131,25 +144,22 @@ pub async fn insert_user(conn: DbConn, new_user: Register) -> Result Result Result { conn.run(move |c| -> Result { - let id: i32 = user::table - .filter(user::username.eq(&login.username)) - .or_filter(user::email.eq(&login.username)) - .select(user::id) + let id: Uuid = users::table + .filter(users::username.eq(&login.username)) + .or_filter(users::email.eq(&login.username)) + .select(users::id) .first(c)?; let pwd: String = pwd::table @@ -175,7 +185,7 @@ pub async fn login(conn: DbConn, login: Login) -> Result Result Result { - Ok(conn.run(move |c| user::table.filter(user::id.eq(id)).first(c)) +pub async fn get_user(conn: DbConn, id: Uuid) -> Result { + Ok(conn.run(move |c| users::table.filter(users::id.eq(id)).first(c)) .await?) } +pub async fn get_all_known_games(conn: &DbConn) -> Result, DatabaseError> { + Ok(conn.run(|c| + known_games::table.load::(c) + ).await?) +} + +pub async fn add_game(conn: &DbConn, game: Game) -> Result { + Ok(conn.run(|c| + diesel::insert_into(known_games::table) + .values(game) + .execute(c) + + ).await?) +} + +pub async fn add_unknown_games(conn: &DbConn, games: &mut Vec) -> Result<(), DatabaseError> { + let all_games = get_all_known_games(conn).await?; + for game in games.iter_mut() { + if !all_games.iter().any(|g| g.name == game.name) { + game.id = Uuid::new_v4(); + add_game(conn, game.clone()).await?; + } + } + Ok(()) +} + + pub fn unique_username( username: &String, - conn: &diesel::SqliteConnection, + conn: &diesel::PgConnection, ) -> Result<(), ValidationError> { - match user::table - .select(count(user::username)) - .filter(user::username.eq(username)) - .execute(conn) + match users::table + .count() + .filter(users::username.eq(username)) + .get_result(conn) { Ok(0) => Ok(()), Ok(_) => Err(ValidationError::new("User already exists")), @@ -213,12 +250,12 @@ pub fn unique_username( pub fn unique_email( email: &String, - conn: &diesel::SqliteConnection, + conn: &diesel::PgConnection, ) -> Result<(), ValidationError> { - match user::table - .select(count(user::email)) - .filter(user::email.eq(email)) - .execute(conn) + match users::table + .count() + .filter(users::email.eq(email)) + .get_result(conn) { Ok(0) => Ok(()), Ok(_) => Err(ValidationError::new("email already exists")), @@ -249,57 +286,60 @@ pub enum Role { } #[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] -#[table_name = "user"] +#[table_name = "users"] pub struct User { - pub id: i32, + pub id: Uuid, pub username: String, pub email: String, pub role: Role, } -#[derive(Serialize, Deserialize, Debug, FromForm, Insertable)] +#[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] +#[table_name = "pwd"] +struct Pwd { + user_id: Uuid, + password: String, +} + +#[derive(Serialize, Deserialize, Debug, Queryable, Clone, Insertable)] #[table_name = "known_games"] -pub struct GameNoId { - pub game: String, -} - -#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)] pub struct Game { - pub id: i32, - pub game: String, + pub id: Uuid, + pub name: String, } -#[derive(Serialize, Deserialize, Debug, Insertable)] +#[derive(Serialize, Deserialize, Debug, Queryable, Insertable)] #[table_name = "gamenight"] -pub struct GameNightNoId { - pub game: String, - pub datetime: String, - pub owner_id: Option -} - -#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)] pub struct GameNight { - pub id: i32, - pub game: String, + pub id: Uuid, + pub name: String, pub datetime: String, - pub owner_id: i32, + pub owner_id: Uuid, } -#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)] +#[derive(Serialize, Deserialize, Debug, Queryable, Insertable)] +#[table_name="gamenight_gamelist"] +pub struct GamenightGameListEntry { + pub gamenight_id: Uuid, + pub game_id: Uuid +} + + +#[derive(Serialize, Deserialize, Debug, Queryable)] pub struct DeleteGameNight { - pub game_id: i32, + pub game_id: Uuid, } #[derive(Serialize, Deserialize, Debug, Validate, Clone)] pub struct Register { #[validate( length(min = 1), - custom(function = "unique_username", arg = "&'v_a diesel::SqliteConnection") + custom(function = "unique_username", arg = "&'v_a diesel::PgConnection") )] pub username: String, #[validate( email, - custom(function = "unique_email", arg = "&'v_a diesel::SqliteConnection") + custom(function = "unique_email", arg = "&'v_a diesel::PgConnection") )] pub email: String, #[validate(length(min = 10), must_match = "password_repeat")] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2d837ec --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.8' + +services: + + db: + container_name: pg_container + image: postgres + restart: always + environment: + POSTGRES_USER: root + POSTGRES_PASSWORD: root + POSTGRES_DB: gamenight + ports: + - "5432:5432" + pgadmin: + container_name: pgadmin4_container + image: dpage/pgadmin4 + restart: always + environment: + PGADMIN_DEFAULT_EMAIL: admin@admin.com + PGADMIN_DEFAULT_PASSWORD: root + ports: + - "5050:80" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 18c39e2..fe24715 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,11 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@material-ui/icons": "^4.11.3", + "@mui/icons-material": "^5.8.0", + "@mui/material": "^5.8.0", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.0.1", "@testing-library/user-event": "^13.5.0", @@ -2171,6 +2176,174 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.9.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz", + "integrity": "sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==", + "dependencies": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/runtime": "^7.13.10", + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.5", + "@emotion/serialize": "^1.0.2", + "babel-plugin-macros": "^2.6.1", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "dependencies": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "dependencies": { + "@emotion/memoize": "^0.7.4" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "node_modules/@emotion/react": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.0.tgz", + "integrity": "sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/cache": "^11.7.1", + "@emotion/serialize": "^1.0.3", + "@emotion/utils": "^1.1.0", + "@emotion/weak-memoize": "^0.2.5", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==", + "dependencies": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "node_modules/@emotion/styled": { + "version": "11.8.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.8.1.tgz", + "integrity": "sha512-OghEVAYBZMpEquHZwuelXcRjRJQOVayvbmNR0zr174NHdmMgrNkLC6TljKC5h9lZLkN5WGrdUcrKlOJ4phhoTQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/is-prop-valid": "^1.1.2", + "@emotion/serialize": "^1.0.2", + "@emotion/utils": "^1.1.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + }, "node_modules/@eslint/eslintrc": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz", @@ -2732,6 +2905,259 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "node_modules/@material-ui/icons": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz", + "integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==", + "dependencies": { + "@babel/runtime": "^7.4.4" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@material-ui/core": "^4.0.0", + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/base": { + "version": "5.0.0-alpha.81", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.81.tgz", + "integrity": "sha512-KJP+RdKBLSbhiAliy1b5xFuoAezawupfIHc/MRtEZdqAmUW0+UFNDXIUDlBKR9zLCjgjQ7eVJsSe0TwAgd8OMQ==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@emotion/is-prop-valid": "^1.1.2", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.8.0", + "@popperjs/core": "^2.11.5", + "clsx": "^1.1.1", + "prop-types": "^15.8.1", + "react-is": "^17.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/icons-material": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.8.0.tgz", + "integrity": "sha512-ScwLxa0q5VYV70Jfc60V/9VD0b9SvIeZ0Jddx2Dt2pBUFFO9vKdrbt9LYiT+4p21Au5NdYIb2XSHj46CLN1v3g==", + "dependencies": { + "@babel/runtime": "^7.17.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.8.0.tgz", + "integrity": "sha512-yvt3sUmUZ1i8SPadRYBCThcB57lBZsvyhC7ufVpRxA3AD39O+WXtXAapEfpDdDkJnnKb5MCimDMwBYgWLmY89Q==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@mui/base": "5.0.0-alpha.81", + "@mui/system": "^5.8.0", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.8.0", + "@types/react-transition-group": "^4.4.4", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "hoist-non-react-statics": "^3.3.2", + "prop-types": "^15.8.1", + "react-is": "^17.0.2", + "react-transition-group": "^4.4.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0", + "react-dom": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.8.0.tgz", + "integrity": "sha512-MjRAneTmCKLR9u2S4jtjLUe6gpHxlbb4g2bqpDJ2PdwlvwsWIUzbc/gVB4dvccljXeWxr5G2M/Co2blXisvFIw==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@mui/utils": "^5.8.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.8.0.tgz", + "integrity": "sha512-Q3spibB8/EgeMYHc+/o3RRTnAYkSl7ROCLhXJ830W8HZ2/iDiyYp16UcxKPurkXvLhUaILyofPVrP3Su2uKsAw==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@emotion/cache": "^11.7.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.8.0.tgz", + "integrity": "sha512-1tEj2S59RjlZ/6JMJMUktQDbV2ev7hyGXqO7dRRUQ7nOJi9qHmCFP0uXj3YS6LbM6hVasgYXJg8GBjbEtfTJvg==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@mui/private-theming": "^5.8.0", + "@mui/styled-engine": "^5.8.0", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.8.0", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0", + "react": "^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.3.tgz", + "integrity": "sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA==", + "peerDependencies": { + "@types/react": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.0.tgz", + "integrity": "sha512-7LgUtCvz78676iC0wpTH7HizMdCrTphhBmRWimIMFrp5Ph6JbDFVuKS1CwYnWWxRyYKL0QzXrDL0lptAU90EXg==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@types/prop-types": "^15.7.5", + "@types/react-is": "^16.7.1 || ^17.0.0", + "prop-types": "^15.8.1", + "react-is": "^17.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2821,6 +3247,15 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -3501,6 +3936,22 @@ "@types/react": "*" } }, + "node_modules/@types/react-is": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", + "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", + "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -5240,6 +5691,14 @@ "mimic-response": "^1.0.0" } }, + "node_modules/clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6275,6 +6734,15 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -7594,6 +8062,11 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -8210,6 +8683,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -13185,6 +13671,21 @@ "node": ">=10" } }, + "node_modules/react-transition-group": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -14339,6 +14840,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -17497,6 +18003,142 @@ "integrity": "sha512-RkYG5KiGNX0fJ5YoI0f4Wfq2Yo74D25Hru4fxTOioYdQvHBxcrrtTTyT5Ozzh2ejcNrhFy7IEts2WyEY7yi5yw==", "requires": {} }, + "@emotion/babel-plugin": { + "version": "11.9.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz", + "integrity": "sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==", + "requires": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/plugin-syntax-jsx": "^7.12.13", + "@babel/runtime": "^7.13.10", + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.5", + "@emotion/serialize": "^1.0.2", + "babel-plugin-macros": "^2.6.1", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + }, + "dependencies": { + "babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "requires": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, + "@emotion/cache": { + "version": "11.7.1", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz", + "integrity": "sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==", + "requires": { + "@emotion/memoize": "^0.7.4", + "@emotion/sheet": "^1.1.0", + "@emotion/utils": "^1.0.0", + "@emotion/weak-memoize": "^0.2.5", + "stylis": "4.0.13" + } + }, + "@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "@emotion/is-prop-valid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz", + "integrity": "sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==", + "requires": { + "@emotion/memoize": "^0.7.4" + } + }, + "@emotion/memoize": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz", + "integrity": "sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==" + }, + "@emotion/react": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.9.0.tgz", + "integrity": "sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/cache": "^11.7.1", + "@emotion/serialize": "^1.0.3", + "@emotion/utils": "^1.1.0", + "@emotion/weak-memoize": "^0.2.5", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.0.3.tgz", + "integrity": "sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==", + "requires": { + "@emotion/hash": "^0.8.0", + "@emotion/memoize": "^0.7.4", + "@emotion/unitless": "^0.7.5", + "@emotion/utils": "^1.0.0", + "csstype": "^3.0.2" + } + }, + "@emotion/sheet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz", + "integrity": "sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==" + }, + "@emotion/styled": { + "version": "11.8.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.8.1.tgz", + "integrity": "sha512-OghEVAYBZMpEquHZwuelXcRjRJQOVayvbmNR0zr174NHdmMgrNkLC6TljKC5h9lZLkN5WGrdUcrKlOJ4phhoTQ==", + "requires": { + "@babel/runtime": "^7.13.10", + "@emotion/babel-plugin": "^11.7.1", + "@emotion/is-prop-valid": "^1.1.2", + "@emotion/serialize": "^1.0.2", + "@emotion/utils": "^1.1.0" + } + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "@emotion/utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz", + "integrity": "sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==" + }, + "@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + }, "@eslint/eslintrc": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.3.tgz", @@ -17922,6 +18564,109 @@ "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==" }, + "@material-ui/icons": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz", + "integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==", + "requires": { + "@babel/runtime": "^7.4.4" + } + }, + "@mui/base": { + "version": "5.0.0-alpha.81", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.81.tgz", + "integrity": "sha512-KJP+RdKBLSbhiAliy1b5xFuoAezawupfIHc/MRtEZdqAmUW0+UFNDXIUDlBKR9zLCjgjQ7eVJsSe0TwAgd8OMQ==", + "requires": { + "@babel/runtime": "^7.17.2", + "@emotion/is-prop-valid": "^1.1.2", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.8.0", + "@popperjs/core": "^2.11.5", + "clsx": "^1.1.1", + "prop-types": "^15.8.1", + "react-is": "^17.0.2" + } + }, + "@mui/icons-material": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.8.0.tgz", + "integrity": "sha512-ScwLxa0q5VYV70Jfc60V/9VD0b9SvIeZ0Jddx2Dt2pBUFFO9vKdrbt9LYiT+4p21Au5NdYIb2XSHj46CLN1v3g==", + "requires": { + "@babel/runtime": "^7.17.2" + } + }, + "@mui/material": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.8.0.tgz", + "integrity": "sha512-yvt3sUmUZ1i8SPadRYBCThcB57lBZsvyhC7ufVpRxA3AD39O+WXtXAapEfpDdDkJnnKb5MCimDMwBYgWLmY89Q==", + "requires": { + "@babel/runtime": "^7.17.2", + "@mui/base": "5.0.0-alpha.81", + "@mui/system": "^5.8.0", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.8.0", + "@types/react-transition-group": "^4.4.4", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "hoist-non-react-statics": "^3.3.2", + "prop-types": "^15.8.1", + "react-is": "^17.0.2", + "react-transition-group": "^4.4.2" + } + }, + "@mui/private-theming": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.8.0.tgz", + "integrity": "sha512-MjRAneTmCKLR9u2S4jtjLUe6gpHxlbb4g2bqpDJ2PdwlvwsWIUzbc/gVB4dvccljXeWxr5G2M/Co2blXisvFIw==", + "requires": { + "@babel/runtime": "^7.17.2", + "@mui/utils": "^5.8.0", + "prop-types": "^15.8.1" + } + }, + "@mui/styled-engine": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.8.0.tgz", + "integrity": "sha512-Q3spibB8/EgeMYHc+/o3RRTnAYkSl7ROCLhXJ830W8HZ2/iDiyYp16UcxKPurkXvLhUaILyofPVrP3Su2uKsAw==", + "requires": { + "@babel/runtime": "^7.17.2", + "@emotion/cache": "^11.7.1", + "prop-types": "^15.8.1" + } + }, + "@mui/system": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.8.0.tgz", + "integrity": "sha512-1tEj2S59RjlZ/6JMJMUktQDbV2ev7hyGXqO7dRRUQ7nOJi9qHmCFP0uXj3YS6LbM6hVasgYXJg8GBjbEtfTJvg==", + "requires": { + "@babel/runtime": "^7.17.2", + "@mui/private-theming": "^5.8.0", + "@mui/styled-engine": "^5.8.0", + "@mui/types": "^7.1.3", + "@mui/utils": "^5.8.0", + "clsx": "^1.1.1", + "csstype": "^3.0.11", + "prop-types": "^15.8.1" + } + }, + "@mui/types": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.1.3.tgz", + "integrity": "sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA==", + "requires": {} + }, + "@mui/utils": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.8.0.tgz", + "integrity": "sha512-7LgUtCvz78676iC0wpTH7HizMdCrTphhBmRWimIMFrp5Ph6JbDFVuKS1CwYnWWxRyYKL0QzXrDL0lptAU90EXg==", + "requires": { + "@babel/runtime": "^7.17.2", + "@types/prop-types": "^15.7.5", + "@types/react-is": "^16.7.1 || ^17.0.0", + "prop-types": "^15.8.1", + "react-is": "^17.0.2" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -17968,6 +18713,11 @@ } } }, + "@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -18486,6 +19236,22 @@ "@types/react": "*" } }, + "@types/react-is": { + "version": "17.0.3", + "resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz", + "integrity": "sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==", + "requires": { + "@types/react": "*" + } + }, + "@types/react-transition-group": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz", + "integrity": "sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==", + "requires": { + "@types/react": "*" + } + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -19765,6 +20531,11 @@ "mimic-response": "^1.0.0" } }, + "clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -20521,6 +21292,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -21498,6 +22278,11 @@ "pkg-dir": "^4.1.0" } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -21916,6 +22701,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -25413,6 +26213,17 @@ } } }, + "react-transition-group": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", @@ -26269,6 +27080,11 @@ "postcss-selector-parser": "^6.0.4" } }, + "stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index c933414..cae5341 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,6 +3,11 @@ "version": "0.1.0", "private": true, "dependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@material-ui/icons": "^4.11.3", + "@mui/icons-material": "^5.8.0", + "@mui/material": "^5.8.0", "@testing-library/jest-dom": "^5.16.4", "@testing-library/react": "^13.0.1", "@testing-library/user-event": "^13.5.0", @@ -21,7 +26,12 @@ "watch": "npm-watch" }, "watch": { - "build": "src/" + "build": { + "patterns": [ + "src/" + ], + "extensions": "js,jsx" + } }, "eslintConfig": { "extends": [ diff --git a/frontend/src/App.js b/frontend/src/App.js index cfc65b8..f1f8416 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -12,6 +12,7 @@ function App() { const [user, setUser] = useState(null); const [gamenights, setGamenights] = useState([]); const [flashData, setFlashData] = useState({}); + const [games, setGames] = useState([]); const handleLogin = (input) => { const requestOptions = { @@ -68,6 +69,27 @@ function App() { } }, [user]) + useEffect(() => { + if (user !== null) { + const requestOptions = { + method: 'GET', + headers: { 'Authorization': `Bearer ${user.jwt}` }, + }; + fetch('api/games', requestOptions) + .then(response => response.json()) + .then(data => { + if(data.result === "Ok") { + setGames(data.games) + } else { + setFlashData({ + type: "Error", + message: data.message + }); + } + }); + } + }, [user]) + useEffect(() => { setUser(JSON.parse(localStorage.getItem(localStorageUserKey))); }, []); @@ -82,7 +104,7 @@ function App() { return ( <> - + ); diff --git a/frontend/src/components/AddGameNight.jsx b/frontend/src/components/AddGameNight.jsx index f52ad23..53667d9 100644 --- a/frontend/src/components/AddGameNight.jsx +++ b/frontend/src/components/AddGameNight.jsx @@ -1,5 +1,8 @@ import React, { useEffect, useState } from 'react'; import DateTime from 'react-datetime'; +import GameAdder from './GameAdder'; + +import Autocomplete from '@mui/material/Autocomplete'; import "react-datetime/css/react-datetime.css"; @@ -7,15 +10,26 @@ function AddGameNight(props) { const [expanded, setExpanded] = useState(false); const [gameName, setGameName] = useState(""); const [date, setDate] = useState(Date.now()); + const [gameList, setGameList] = useState([]); + + const emptyUuid = "00000000-0000-0000-0000-000000000000"; + + //temp hack: + props.games = [{id: emptyUuid, name: "mystic vale"}, {id: emptyUuid, name: "Crew"}]; + const handleNameChange = (event) => { setGameName(event.target.value); }; - const handleDateChange = (event) => { - setDate(event.target.value); + const onDateChange = (date) => { + setDate(date); }; + const onGamesListChange = (gameList) => { + setGameList(gameList); + } + useEffect(() => { if(!expanded) { setGameName(""); @@ -27,8 +41,9 @@ function AddGameNight(props) { event.preventDefault(); if (props.user !== null) { let input = { - game: gameName, + name: gameName, datetime: date, + game_list: gameList, } const requestOptions = { @@ -40,7 +55,7 @@ function AddGameNight(props) { body: JSON.stringify(input) }; - fetch('api/gamenight', requestOptions) + fetch('api/gamenights', requestOptions) .then(response => response.json()) .then(data => { if(data.result !== "Ok") { @@ -62,7 +77,7 @@ function AddGameNight(props) { if(expanded) { return (
-
+ { e.preventDefault(); }}>
Gamenight @@ -71,8 +86,10 @@ function AddGameNight(props) { value={gameName} onChange={handleNameChange} /> - { setDate(value) }} value={date} /> + + +
diff --git a/frontend/src/components/GameAdder.jsx b/frontend/src/components/GameAdder.jsx new file mode 100644 index 0000000..8d45dfe --- /dev/null +++ b/frontend/src/components/GameAdder.jsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import TextField from '@mui/material/TextField'; +import Chip from '@mui/material/Chip'; +import Autocomplete from '@mui/material/Autocomplete'; + +export default function GameAdder(props) { + + const [value, setValue] = React.useState([]); + + const emptyUuid = "00000000-0000-0000-0000-000000000000"; + + return ( + option.name} + freeSolo + selectOnFocus + renderTags={(value, getTagProps) => + value.map((option, index) => ( + + )) + } + renderInput={(params) => ( + + )} + onChange={(event, newValue) => { + newValue = newValue.map(option => { + if (typeof option === 'string') { + var match = props.games.find(g => g.name.toLowerCase() === option.toLowerCase()); + if(match !== undefined) { + return match + } else { + return {id: emptyUuid, name: option}; + } + } + else { + return option; + } + }); + + newValue = newValue.filter((value, index, self) => + index === self.findIndex((t) => ( + t.id === value.id && t.name === value.name + )) + ); + setValue(newValue); + props.onChange(newValue); + }} + + /> + ); +} \ No newline at end of file diff --git a/frontend/src/components/Gamenights.jsx b/frontend/src/components/Gamenights.jsx index f53e773..3a5d35a 100644 --- a/frontend/src/components/Gamenights.jsx +++ b/frontend/src/components/Gamenights.jsx @@ -17,7 +17,7 @@ function Gamenights(props) { body: JSON.stringify(input) }; - fetch('api/gamenight', requestOptions) + fetch('api/gamenights', requestOptions) .then(response => response.json()) .then(data => { if(data.result !== "Ok") { @@ -34,7 +34,7 @@ function Gamenights(props) { let gamenights = props.gamenights.map(g => (
  • - {g.game} + {g.name} {(props.user.id === g.owner_id || props.user.role === "Admin") &&