Added reviews. Added pricing.
Build and Publish / Build and Publish Docker Image (push) Successful in 6m27s
Build and Publish / Build and Publish Docker Image (push) Successful in 6m27s
This commit is contained in:
+76
-8
@@ -1,16 +1,16 @@
|
||||
use cot::Template;
|
||||
use cot::db::{Auto, Database, Model};
|
||||
use cot::html::Html;
|
||||
use cot::request::Request;
|
||||
use cot::request::extractors::Path;
|
||||
use cot::response::{IntoResponse, Redirect, Response};
|
||||
use cot::router::{Route, Router};
|
||||
use cot::Template;
|
||||
use serde::Deserialize;
|
||||
|
||||
use cot::db::query;
|
||||
|
||||
use crate::i18n::{Lang, Translations};
|
||||
use crate::models::{Client, Lead, Media, Setting, User, Visit};
|
||||
use crate::models::{Client, Lead, Media, Setting, Testimonial, User, Visit};
|
||||
use crate::telegram;
|
||||
|
||||
fn detect_lang(request: &Request) -> Lang {
|
||||
@@ -70,6 +70,8 @@ struct LandingTemplate<'a> {
|
||||
t: &'a Translations,
|
||||
lang: Lang,
|
||||
contact_info: String,
|
||||
pricing_info: String,
|
||||
testimonials: Vec<Testimonial>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Template)]
|
||||
@@ -87,7 +89,23 @@ async fn landing_page(request: Request, db: Database) -> cot::Result<Response> {
|
||||
.await?
|
||||
.map(|s| s.value)
|
||||
.unwrap_or_default();
|
||||
let body = LandingTemplate { t: lang.t(), lang, contact_info }.render()?;
|
||||
let pricing_key = "pricing_info".to_string();
|
||||
let pricing_info = query!(Setting, $key == pricing_key)
|
||||
.get(&db)
|
||||
.await?
|
||||
.map(|s| s.value)
|
||||
.unwrap_or_default();
|
||||
let mut testimonials = Testimonial::objects().all(&db).await?;
|
||||
testimonials.retain(|t| t.status == "active");
|
||||
testimonials.sort_by(|a, b| a.sort_order.cmp(&b.sort_order));
|
||||
let body = LandingTemplate {
|
||||
t: lang.t(),
|
||||
lang,
|
||||
contact_info,
|
||||
pricing_info,
|
||||
testimonials,
|
||||
}
|
||||
.render()?;
|
||||
html_response(body, lang)
|
||||
}
|
||||
|
||||
@@ -173,10 +191,12 @@ async fn client_portal(
|
||||
let today = chrono::Utc::now().date_naive();
|
||||
|
||||
let mut visits = Visit::objects().all(&db).await?;
|
||||
visits.retain(|v| {
|
||||
v.client_id.primary_key().unwrap() == client_id && v.status != "cancelled"
|
||||
visits.retain(|v| v.client_id.primary_key().unwrap() == client_id && v.status != "cancelled");
|
||||
visits.sort_by(|a, b| {
|
||||
a.visit_date
|
||||
.cmp(&b.visit_date)
|
||||
.then(a.time_start.cmp(&b.time_start))
|
||||
});
|
||||
visits.sort_by(|a, b| a.visit_date.cmp(&b.visit_date).then(a.time_start.cmp(&b.time_start)));
|
||||
|
||||
let users = User::objects().all(&db).await?;
|
||||
let all_media = Media::objects().all(&db).await?;
|
||||
@@ -200,7 +220,11 @@ async fn client_portal(
|
||||
})
|
||||
.cloned()
|
||||
.collect();
|
||||
PortalVisit { visit: v, admin_name, media }
|
||||
PortalVisit {
|
||||
visit: v,
|
||||
admin_name,
|
||||
media,
|
||||
}
|
||||
};
|
||||
|
||||
let mut upcoming = Vec::new();
|
||||
@@ -258,7 +282,12 @@ async fn submit_feedback(
|
||||
}
|
||||
}
|
||||
|
||||
Redirect::new(format!("/client/{}?lang={}&feedback=ok", token_clone, lang.code())).into_response()
|
||||
Redirect::new(format!(
|
||||
"/client/{}?lang={}&feedback=ok",
|
||||
token_clone,
|
||||
lang.code()
|
||||
))
|
||||
.into_response()
|
||||
}
|
||||
|
||||
/// Serve media files for the client portal (no auth required, but only via token).
|
||||
@@ -303,10 +332,49 @@ async fn portal_media(
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve_testimonial_image(
|
||||
_request: Request,
|
||||
db: Database,
|
||||
Path(id): Path<i64>,
|
||||
) -> cot::Result<Response> {
|
||||
let testimonial = match query!(Testimonial, $id == id).get(&db).await? {
|
||||
Some(t) => t,
|
||||
None => return Html::new("404").into_response(),
|
||||
};
|
||||
let path = match &testimonial.image_path {
|
||||
Some(p) => p.clone(),
|
||||
None => return Html::new("404").into_response(),
|
||||
};
|
||||
match tokio::fs::read(&path).await {
|
||||
Ok(data) => {
|
||||
let content_type = match path.rsplit('.').next().unwrap_or("") {
|
||||
"jpg" | "jpeg" => "image/jpeg",
|
||||
"png" => "image/png",
|
||||
"webp" => "image/webp",
|
||||
"heic" | "heif" => "image/heic",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
let body = cot::Body::fixed(data);
|
||||
let mut resp = Response::new(body);
|
||||
resp.headers_mut()
|
||||
.insert("content-type", content_type.parse().unwrap());
|
||||
resp.headers_mut()
|
||||
.insert("cache-control", "public, max-age=86400".parse().unwrap());
|
||||
Ok(resp)
|
||||
}
|
||||
Err(_) => Html::new("404").into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn public_router() -> Router {
|
||||
Router::with_urls([
|
||||
Route::with_handler_and_name("/", landing_page, "landing"),
|
||||
Route::with_handler_and_name("/submit", submit_lead, "submit-lead"),
|
||||
Route::with_handler_and_name(
|
||||
"/testimonial-image/{id}",
|
||||
serve_testimonial_image,
|
||||
"testimonial-image",
|
||||
),
|
||||
Route::with_handler_and_name("/client/{token}", client_portal, "client-portal"),
|
||||
Route::with_handler_and_name(
|
||||
"/client/{token}/{visit_id}/feedback",
|
||||
|
||||
Reference in New Issue
Block a user