it's a good moment to commit
This commit is contained in:
parent
14c28762bf
commit
c3cf86f6e9
19 changed files with 249 additions and 175 deletions
4
Caddyfile
Normal file
4
Caddyfile
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
:30002 {
|
||||||
|
reverse_proxy http://localhost:30000
|
||||||
|
reverse_proxy /api http://localhost:30001
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
# see https://diesel.rs/guides/configuring-diesel-cli
|
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
[print_schema]
|
[print_schema]
|
||||||
file = "src/schema.rs"
|
file = "src/database/schema.rs"
|
||||||
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||||
|
|
||||||
[migrations_directory]
|
[migrations_directory]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
micronfig::config! {
|
micronfig::config! {
|
||||||
DATABASE_URL: String,
|
DATABASE_URL: String,
|
||||||
BIND_ADDRESS: String > std::net::SocketAddr,
|
BACKEND_BIND_ADDRESS: String > std::net::SocketAddr,
|
||||||
TELEGRAM_API_KEY: String,
|
TELEGRAM_API_KEY: String,
|
||||||
TELEGRAM_WEBHOOK_URL: String > url::Url,
|
TELEGRAM_WEBHOOK_URL: String > url::Url,
|
||||||
}
|
}
|
||||||
|
|
1
holycow_backend/src/database/migrations.rs
Normal file
1
holycow_backend/src/database/migrations.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub const MIGRATIONS: diesel_migrations::EmbeddedMigrations = diesel_migrations::embed_migrations!();
|
3
holycow_backend/src/database/mod.rs
Normal file
3
holycow_backend/src/database/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod model;
|
||||||
|
pub mod migrations;
|
||||||
|
mod schema;
|
|
@ -3,13 +3,14 @@ use chrono::{DateTime, Utc};
|
||||||
use diesel::{AsExpression, BoolExpressionMethods, FromSqlRow, Identifiable, Insertable, OptionalExtension, PgConnection, QueryDsl, QueryResult, Queryable, QueryableByName, RunQueryDsl, Selectable, SelectableHelper};
|
use diesel::{AsExpression, BoolExpressionMethods, FromSqlRow, Identifiable, Insertable, OptionalExtension, PgConnection, QueryDsl, QueryResult, Queryable, QueryableByName, RunQueryDsl, Selectable, SelectableHelper};
|
||||||
use diesel::backend::Backend;
|
use diesel::backend::Backend;
|
||||||
use diesel::deserialize::FromSql;
|
use diesel::deserialize::FromSql;
|
||||||
|
use diesel::dsl::insert_into;
|
||||||
use diesel::pg::Pg;
|
use diesel::pg::Pg;
|
||||||
use diesel::serialize::ToSql;
|
use diesel::serialize::ToSql;
|
||||||
use diesel::sql_types as sql;
|
use diesel::sql_types as sql;
|
||||||
use diesel::serialize::Output as DieselOutput;
|
use diesel::serialize::Output as DieselOutput;
|
||||||
use diesel::ExpressionMethods;
|
use diesel::ExpressionMethods;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::schema;
|
use crate::database::schema;
|
||||||
|
|
||||||
#[derive(Debug, Clone, FromSqlRow, AsExpression, Serialize, Deserialize)]
|
#[derive(Debug, Clone, FromSqlRow, AsExpression, Serialize, Deserialize)]
|
||||||
#[diesel(sql_type = sql::BigInt)]
|
#[diesel(sql_type = sql::BigInt)]
|
||||||
|
@ -142,6 +143,18 @@ impl ToSql<schema::sql_types::OutcomeT, Pg> for Outcome {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
|
pub fn total(conn: &mut PgConnection) -> QueryResult<i64> {
|
||||||
|
schema::players::table
|
||||||
|
.select(diesel::dsl::count_star())
|
||||||
|
.get_result::<i64>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all(conn: &mut PgConnection) -> QueryResult<Vec<Self>> {
|
||||||
|
schema::players::table
|
||||||
|
.select(Self::as_select())
|
||||||
|
.get_results::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_by_id(conn: &mut PgConnection, player_id: i32) -> QueryResult<Option<Self>> {
|
pub fn get_by_id(conn: &mut PgConnection, player_id: i32) -> QueryResult<Option<Self>> {
|
||||||
schema::players::table
|
schema::players::table
|
||||||
.select(Self::as_select())
|
.select(Self::as_select())
|
||||||
|
@ -174,6 +187,13 @@ impl Match {
|
||||||
.get_result::<i64>(conn)
|
.get_result::<i64>(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn all(conn: &mut PgConnection) -> QueryResult<Vec<Self>> {
|
||||||
|
schema::matches::table
|
||||||
|
.select(Self::as_select())
|
||||||
|
.order_by(schema::matches::instant)
|
||||||
|
.get_results::<Self>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn played_by_count(conn: &mut PgConnection, player_id: i32) -> QueryResult<i64> {
|
pub fn played_by_count(conn: &mut PgConnection, player_id: i32) -> QueryResult<i64> {
|
||||||
schema::matches::table
|
schema::matches::table
|
||||||
.select(diesel::dsl::count_star())
|
.select(diesel::dsl::count_star())
|
||||||
|
@ -192,3 +212,19 @@ impl Match {
|
||||||
.get_result::<i64>(conn)
|
.get_result::<i64>(conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PlayerI {
|
||||||
|
pub fn insert(self, conn: &mut PgConnection) -> QueryResult<Player> {
|
||||||
|
insert_into(schema::players::table)
|
||||||
|
.values(&[self])
|
||||||
|
.get_result::<Player>(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MatchI {
|
||||||
|
pub fn insert(self, conn: &mut PgConnection) -> QueryResult<Match> {
|
||||||
|
insert_into(schema::matches::table)
|
||||||
|
.values(&[self])
|
||||||
|
.get_result::<Match>(conn)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,17 @@
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use axum::extract::Path;
|
|
||||||
use axum::http::StatusCode;
|
|
||||||
use axum::Json;
|
|
||||||
use diesel::{Connection, PgConnection};
|
use diesel::{Connection, PgConnection};
|
||||||
use diesel_migrations::MigrationHarness;
|
use diesel_migrations::MigrationHarness;
|
||||||
use serde::Serialize;
|
use teloxide::dispatching::{DefaultKey, MessageFilterExt};
|
||||||
use teloxide::{dptree};
|
|
||||||
use teloxide::dispatching::{DefaultKey, MessageFilterExt, UpdateFilterExt};
|
|
||||||
use teloxide::error_handlers::LoggingErrorHandler;
|
use teloxide::error_handlers::LoggingErrorHandler;
|
||||||
use teloxide::types::{Message, WebAppData};
|
use teloxide::types::Message;
|
||||||
use teloxide::update_listeners::webhooks::Options;
|
use teloxide::update_listeners::webhooks::Options;
|
||||||
use crate::types::TelegramId;
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
mod schema;
|
mod database;
|
||||||
mod types;
|
mod routes;
|
||||||
|
mod telegram;
|
||||||
pub const MIGRATIONS: diesel_migrations::EmbeddedMigrations = diesel_migrations::embed_migrations!();
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<Infallible> {
|
async fn main() -> anyhow::Result<Infallible> {
|
||||||
|
@ -30,7 +23,7 @@ async fn main() -> anyhow::Result<Infallible> {
|
||||||
log::trace!("Database URL is: {db:?}");
|
log::trace!("Database URL is: {db:?}");
|
||||||
|
|
||||||
log::trace!("Determining bind address...");
|
log::trace!("Determining bind address...");
|
||||||
let bind_address = config::BIND_ADDRESS();
|
let bind_address = config::BACKEND_BIND_ADDRESS();
|
||||||
log::trace!("Bind address is: {bind_address:?}");
|
log::trace!("Bind address is: {bind_address:?}");
|
||||||
|
|
||||||
log::trace!("Connecting to: {db:?}");
|
log::trace!("Connecting to: {db:?}");
|
||||||
|
@ -43,7 +36,7 @@ async fn main() -> anyhow::Result<Infallible> {
|
||||||
};
|
};
|
||||||
|
|
||||||
log::trace!("Running migrations...");
|
log::trace!("Running migrations...");
|
||||||
if let Err(e) = db.run_pending_migrations(MIGRATIONS) {
|
if let Err(e) = db.run_pending_migrations(database::migrations::MIGRATIONS) {
|
||||||
log::error!("Failed to perform migration: {e:#?}");
|
log::error!("Failed to perform migration: {e:#?}");
|
||||||
exit(2);
|
exit(2);
|
||||||
};
|
};
|
||||||
|
@ -55,7 +48,7 @@ async fn main() -> anyhow::Result<Infallible> {
|
||||||
let (telegram_listener, _telegram_stop, telegram_router) = teloxide::update_listeners::webhooks::axum_to_router(
|
let (telegram_listener, _telegram_stop, telegram_router) = teloxide::update_listeners::webhooks::axum_to_router(
|
||||||
bot.clone(),
|
bot.clone(),
|
||||||
Options {
|
Options {
|
||||||
address: config::BIND_ADDRESS().clone(),
|
address: config::BACKEND_BIND_ADDRESS().clone(),
|
||||||
url: config::TELEGRAM_WEBHOOK_URL().clone(),
|
url: config::TELEGRAM_WEBHOOK_URL().clone(),
|
||||||
path: "/".to_string(),
|
path: "/".to_string(),
|
||||||
certificate: None,
|
certificate: None,
|
||||||
|
@ -67,16 +60,28 @@ async fn main() -> anyhow::Result<Infallible> {
|
||||||
|
|
||||||
log::trace!("Creating Axum router...");
|
log::trace!("Creating Axum router...");
|
||||||
let app = axum::Router::new()
|
let app = axum::Router::new()
|
||||||
.route("/players/holycow/:player_id/results", axum::routing::get(results_by_id_handler))
|
.route("/api/results/",
|
||||||
.route("/players/telegram/:telegram_id/results", axum::routing::get(results_by_telegram_id_handler))
|
axum::routing::get(routes::results::get_all)
|
||||||
.nest("/telegram/webhook", telegram_router)
|
)
|
||||||
|
.route("/api/results/holycow/:player_id",
|
||||||
|
axum::routing::get(routes::results::get_by_id)
|
||||||
|
)
|
||||||
|
.route("/api/results/telegram/:telegram_id",
|
||||||
|
axum::routing::get(routes::results::get_by_telegram_id)
|
||||||
|
)
|
||||||
|
.route("/api/matches/",
|
||||||
|
axum::routing::get(routes::matches::get_all)
|
||||||
|
)
|
||||||
|
.nest("/telegram/webhook",
|
||||||
|
telegram_router
|
||||||
|
)
|
||||||
;
|
;
|
||||||
|
|
||||||
log::trace!("Setting up Telegram dispatcher...");
|
log::trace!("Setting up Telegram dispatcher...");
|
||||||
let mut telegram_dispatcher = teloxide::dispatching::Dispatcher::<teloxide::Bot, anyhow::Error, DefaultKey>::builder(
|
let mut telegram_dispatcher = teloxide::dispatching::Dispatcher::<teloxide::Bot, anyhow::Error, DefaultKey>::builder(
|
||||||
bot.clone(),
|
bot.clone(),
|
||||||
Message::filter_web_app_data()
|
Message::filter_web_app_data()
|
||||||
.endpoint(telegram_web_app_handler)
|
.endpoint(telegram::webapp::process_data)
|
||||||
)
|
)
|
||||||
.default_handler(|u| async move {
|
.default_handler(|u| async move {
|
||||||
log::trace!("Unhandled update: {u:#?}")
|
log::trace!("Unhandled update: {u:#?}")
|
||||||
|
@ -100,71 +105,3 @@ async fn main() -> anyhow::Result<Infallible> {
|
||||||
log::error!("Server exited!");
|
log::error!("Server exited!");
|
||||||
exit(1)
|
exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize)]
|
|
||||||
struct ResultsResponse {
|
|
||||||
played: i64,
|
|
||||||
won: i64,
|
|
||||||
rating: f64,
|
|
||||||
uncertainty: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn results(
|
|
||||||
conn: &mut PgConnection,
|
|
||||||
player: types::Player,
|
|
||||||
) -> Result<Json<ResultsResponse>, StatusCode> {
|
|
||||||
let played = player.played_count(conn)
|
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
||||||
|
|
||||||
let won = player.won_count(conn)
|
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
||||||
|
|
||||||
let rating = match player.competitive {
|
|
||||||
false => 0.0,
|
|
||||||
true => player.wenglin.0.rating,
|
|
||||||
};
|
|
||||||
|
|
||||||
let uncertainty = match player.competitive {
|
|
||||||
false => 0.0,
|
|
||||||
true => player.wenglin.0.uncertainty,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Json(ResultsResponse {
|
|
||||||
played, won, rating, uncertainty
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
|
||||||
async fn results_by_id_handler(
|
|
||||||
Path(player_id): Path<i32>,
|
|
||||||
) -> Result<Json<ResultsResponse>, StatusCode> {
|
|
||||||
let mut conn = PgConnection::establish(config::DATABASE_URL())
|
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
||||||
|
|
||||||
let player = types::Player::get_by_id(&mut conn, player_id)
|
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
|
||||||
.ok_or(StatusCode::NOT_FOUND)?;
|
|
||||||
|
|
||||||
results(&mut conn, player)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
|
||||||
async fn results_by_telegram_id_handler(
|
|
||||||
Path(telegram_id): Path<TelegramId>,
|
|
||||||
) -> Result<Json<ResultsResponse>, StatusCode> {
|
|
||||||
let mut conn = PgConnection::establish(config::DATABASE_URL())
|
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
|
||||||
|
|
||||||
let player = types::Player::get_by_telegram_id(&mut conn, telegram_id)
|
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
|
||||||
.ok_or(StatusCode::NOT_FOUND)?;
|
|
||||||
|
|
||||||
results(&mut conn, player)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn telegram_web_app_handler(
|
|
||||||
web_app_data: WebAppData,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
log::trace!("{web_app_data:#?}");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
18
holycow_backend/src/routes/matches.rs
Normal file
18
holycow_backend/src/routes/matches.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::Json;
|
||||||
|
use diesel::{Connection, PgConnection};
|
||||||
|
use crate::config;
|
||||||
|
use crate::database::model::Match;
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
pub async fn get_all()
|
||||||
|
-> Result<Json<Vec<Match>>, StatusCode>
|
||||||
|
{
|
||||||
|
let mut conn = PgConnection::establish(config::DATABASE_URL())
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
let matches = Match::all(&mut conn)
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
Ok(Json(matches))
|
||||||
|
}
|
2
holycow_backend/src/routes/mod.rs
Normal file
2
holycow_backend/src/routes/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod results;
|
||||||
|
pub mod matches;
|
53
holycow_backend/src/routes/results.rs
Normal file
53
holycow_backend/src/routes/results.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use axum::extract::Path;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use axum::Json;
|
||||||
|
use diesel::{Connection, PgConnection};
|
||||||
|
use model::Player;
|
||||||
|
use crate::config;
|
||||||
|
use crate::database::model;
|
||||||
|
use crate::database::model::TelegramId;
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
pub async fn get_all()
|
||||||
|
-> Result<Json<Vec<Player>>, StatusCode>
|
||||||
|
{
|
||||||
|
let mut conn = PgConnection::establish(config::DATABASE_URL())
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
let players = Player::all(&mut conn)
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
Ok(Json(players))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
pub async fn get_by_id(
|
||||||
|
Path(player_id): Path<i32>,
|
||||||
|
)
|
||||||
|
-> Result<Json<Player>, StatusCode>
|
||||||
|
{
|
||||||
|
let mut conn = PgConnection::establish(config::DATABASE_URL())
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
let player = Player::get_by_id(&mut conn, player_id)
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||||
|
.ok_or(StatusCode::NOT_FOUND)?;
|
||||||
|
|
||||||
|
Ok(Json(player))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
pub async fn get_by_telegram_id(
|
||||||
|
Path(telegram_id): Path<TelegramId>,
|
||||||
|
)
|
||||||
|
-> Result<Json<Player>, StatusCode>
|
||||||
|
{
|
||||||
|
let mut conn = PgConnection::establish(config::DATABASE_URL())
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|
||||||
|
let player = Player::get_by_telegram_id(&mut conn, telegram_id)
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||||
|
.ok_or(StatusCode::NOT_FOUND)?;
|
||||||
|
|
||||||
|
Ok(Json(player))
|
||||||
|
}
|
1
holycow_backend/src/telegram/mod.rs
Normal file
1
holycow_backend/src/telegram/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod webapp;
|
8
holycow_backend/src/telegram/webapp.rs
Normal file
8
holycow_backend/src/telegram/webapp.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
use teloxide::types::WebAppData;
|
||||||
|
|
||||||
|
pub async fn process_data(
|
||||||
|
web_app_data: WebAppData,
|
||||||
|
) -> anyhow::Result<()> {
|
||||||
|
log::trace!("{web_app_data:#?}");
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
/** @type {import("next").NextConfig} */
|
|
||||||
const nextConfig = {
|
|
||||||
output: "export"
|
|
||||||
};
|
|
||||||
|
|
||||||
export default nextConfig;
|
|
7
holycow_frontend/next.config.ts
Normal file
7
holycow_frontend/next.config.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import {NextConfig} from "next"
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
output: "export",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
|
@ -3,7 +3,7 @@
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack --port 30000",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
|
|
|
@ -38,9 +38,7 @@ export default function RootLayout({ children }) {
|
||||||
<p>
|
<p>
|
||||||
© Stefano Pigozzi
|
© Stefano Pigozzi
|
||||||
-
|
-
|
||||||
A quanto pare non posso mettere link esterni qui
|
che cursata, non ha senso, che cursata
|
||||||
-
|
|
||||||
Garasauto
|
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,89 +1,18 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { StatPanel } from "@/components/StatPanel";
|
|
||||||
import { useTelegram } from "@/components/useTelegram";
|
import { useTelegram } from "@/components/useTelegram";
|
||||||
import {useEffect, useMemo} from "react"
|
import {useEffect, useMemo} from "react"
|
||||||
import classNames from "classnames";
|
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const telegram = useTelegram()
|
const telegram = useTelegram()
|
||||||
const telegramData = telegram?.WebApp?.initDataUnsafe
|
const telegramData = telegram?.WebApp?.initDataUnsafe
|
||||||
const userId = telegramData?.user?.id
|
const userId = telegramData?.user?.id
|
||||||
const userName = telegramData?.user?.first_name ?? "???"
|
const userName = telegramData?.user?.first_name
|
||||||
|
const startParam = telegramData?.start_param
|
||||||
|
|
||||||
const resultsData = undefined
|
const resultsData = undefined
|
||||||
const resultsError = undefined
|
const resultsError = undefined
|
||||||
|
|
||||||
useEffect(() => {
|
return <main>
|
||||||
if(telegramData.start_param === "report") {
|
</main>
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
}, [telegramData])
|
|
||||||
|
|
||||||
const contents = useMemo(() => {
|
|
||||||
if(resultsError) {
|
|
||||||
return resultsError.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
const played = resultsData?.["played"] ?? 0
|
|
||||||
const wins = resultsData?.["wins"] ?? 0
|
|
||||||
const rating = resultsData?.["rating"] ?? 0
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={"chapter-1"}>
|
|
||||||
<div className={"panel box"}>
|
|
||||||
<h3>
|
|
||||||
{userName}
|
|
||||||
</h3>
|
|
||||||
<div className={"chapter-3"}>
|
|
||||||
<StatPanel
|
|
||||||
name={"Giocate"}
|
|
||||||
value={(
|
|
||||||
<data
|
|
||||||
className={classNames({
|
|
||||||
"fade": played === 0
|
|
||||||
})}
|
|
||||||
value={played}
|
|
||||||
>
|
|
||||||
{played}
|
|
||||||
</data>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<StatPanel
|
|
||||||
name={"Vinte"}
|
|
||||||
value={(
|
|
||||||
<data
|
|
||||||
className={classNames({
|
|
||||||
"fade": wins === 0
|
|
||||||
})}
|
|
||||||
value={wins}
|
|
||||||
>
|
|
||||||
{wins}
|
|
||||||
</data>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<StatPanel
|
|
||||||
name={"Rating"}
|
|
||||||
value={(
|
|
||||||
<data
|
|
||||||
className={classNames({
|
|
||||||
"fade": rating === 0
|
|
||||||
})}
|
|
||||||
value={rating}
|
|
||||||
>
|
|
||||||
{rating === 0 ? "-" : rating}
|
|
||||||
</data>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}, [userName, resultsData, resultsError])
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<main>
|
|
||||||
{contents}
|
|
||||||
</main>
|
|
||||||
</>
|
|
||||||
}
|
}
|
||||||
|
|
83
holycow_frontend/src/components/ProfileBox.tsx
Normal file
83
holycow_frontend/src/components/ProfileBox.tsx
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import {StatPanel} from "@/components/StatPanel"
|
||||||
|
import classNames from "classnames"
|
||||||
|
|
||||||
|
|
||||||
|
export type ProfileBoxProps = {
|
||||||
|
userName: string,
|
||||||
|
played: number,
|
||||||
|
won: number,
|
||||||
|
rating: number,
|
||||||
|
uncertainty: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function ProfileBox({userName, played, won, rating, uncertainty}: ProfileBoxProps) {
|
||||||
|
return (
|
||||||
|
<div className={"chapter-1"}>
|
||||||
|
<div className={"panel box"}>
|
||||||
|
<h3>
|
||||||
|
{userName}
|
||||||
|
</h3>
|
||||||
|
<div className={"chapter-4"}>
|
||||||
|
<StatPanel
|
||||||
|
name={"Giocate"}
|
||||||
|
value={(
|
||||||
|
<data
|
||||||
|
className={classNames({
|
||||||
|
"fade": played === 0
|
||||||
|
})}
|
||||||
|
value={played}
|
||||||
|
>
|
||||||
|
{played}
|
||||||
|
</data>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<StatPanel
|
||||||
|
name={"Vinte"}
|
||||||
|
value={(
|
||||||
|
<data
|
||||||
|
className={classNames({
|
||||||
|
"fade": won === 0
|
||||||
|
})}
|
||||||
|
value={won}
|
||||||
|
>
|
||||||
|
{won}
|
||||||
|
</data>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<StatPanel
|
||||||
|
name={"Punteggio"}
|
||||||
|
value={(
|
||||||
|
<span
|
||||||
|
className={classNames({
|
||||||
|
"fade": rating === 0
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{rating === 0
|
||||||
|
?
|
||||||
|
<>
|
||||||
|
<data value={rating}>
|
||||||
|
-
|
||||||
|
</data>
|
||||||
|
</>
|
||||||
|
:
|
||||||
|
<>
|
||||||
|
<data value={rating}>
|
||||||
|
{rating}
|
||||||
|
</data>
|
||||||
|
<span>
|
||||||
|
±
|
||||||
|
</span>
|
||||||
|
<data value={uncertainty}>
|
||||||
|
{uncertainty}
|
||||||
|
</data>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in a new issue