Render individual product pages

This commit is contained in:
~erin 2022-04-01 14:19:09 -04:00
parent 1a1a5a3451
commit 9615f13199
No known key found for this signature in database
GPG Key ID: DA70E064A8C70F44
4 changed files with 161 additions and 11 deletions

View File

@ -8,7 +8,7 @@ use crate::authenticate::*;
use crate::database::*;
use crate::structures::*;
use crate::html::render_pages;
use crate::html::{render_product, render_products};
use argon2::{password_hash::SaltString, Argon2};
// Endpoint to add a new product
@ -38,7 +38,8 @@ pub fn new_product(argon2: &State<Argon2>, request: Form<ProductRequest>) -> Jso
}
}
render_pages();
render_products();
render_product(new_product);
return Json(Status {
// Return a JSON status
@ -254,6 +255,8 @@ pub fn update_product(argon2: &State<Argon2>, update: Form<Update<'_>>) -> Json<
}
}
render_product(new_product);
return Json(Status {
// Return a json success status
status: "success".to_string(),
@ -283,6 +286,8 @@ pub fn update_product(argon2: &State<Argon2>, update: Form<Update<'_>>) -> Json<
}
}
render_product(new_product);
return Json(Status {
status: "success".to_string(),
reason: "stock changed".to_string(),

View File

@ -7,6 +7,24 @@ use std::sync::Mutex;
use tera::{Context, Tera};
use toml::Value;
// Struct for data needed for rendering a product
struct ProductRenderObject {
url: String,
tera: Tera,
product: Product,
}
// Struct for data needed for rendering
struct RenderObject<'a> {
url: String,
tera: Tera,
subtitle: &'a str,
active_page: &'a str,
title: &'a str,
description: &'a str,
products: Option<Vec<Product>>,
}
// Navigation bar info
#[derive(Serialize)]
struct NavLink {
href: String,
@ -14,6 +32,7 @@ struct NavLink {
active: AtomicBool,
}
// TOML config file
#[derive(Serialize, Deserialize)]
struct Config {
title: String,
@ -24,6 +43,7 @@ struct Config {
products: Products,
}
// Products field in TOML
#[derive(Serialize, Deserialize)]
struct Products {
label: String,
@ -34,6 +54,7 @@ struct Products {
progestogens: Page,
}
// TOML page info
#[derive(Serialize, Deserialize)]
struct Page {
label: String,
@ -41,6 +62,7 @@ struct Page {
title: String,
}
// Base info for rendering a page
struct PageBase {
config: Config,
tera: Tera,
@ -48,6 +70,7 @@ struct PageBase {
product_classes: Vec<NavLink>,
}
// Build the base info
fn build_base() -> PageBase {
// Read in TOML
let mut config_file = std::fs::File::open("config.toml").unwrap();
@ -89,6 +112,7 @@ fn build_base() -> PageBase {
let nav_elements = vec![nav_home, nav_about, nav_products, nav_contact];
// Construct the product classes
let estrogens = NavLink {
href: config.products.estrogens.href.clone(),
text: config.products.estrogens.label.clone(),
@ -113,6 +137,8 @@ fn build_base() -> PageBase {
product_classes,
};
}
// Render all the pages
pub fn render_pages() {
let base = build_base();
// Render pages
@ -164,6 +190,12 @@ pub fn render_pages() {
&base.product_classes,
);
render_products();
}
// Render the products page
pub fn render_products() {
let base = build_base();
let products_vec: Option<Vec<Product>>;
match read_all_products() {
Ok(prods) => products_vec = Some(prods),
@ -186,14 +218,56 @@ pub fn render_pages() {
);
}
struct RenderObject<'a> {
url: String,
tera: Tera,
subtitle: &'a str,
active_page: &'a str,
title: &'a str,
description: &'a str,
products: Option<Vec<Product>>,
// Render a specific product page
pub fn render_product(product: Product) {
let base = build_base();
let obj = ProductRenderObject {
url: format!("products/{}/index.html", product.short_name),
tera: base.tera.clone(),
product,
};
let url = format!("static/{}", &obj.url);
let url_str = url.as_str();
let prefix = std::path::Path::new(url_str).parent().unwrap();
std::fs::create_dir_all(prefix).unwrap();
let mut output_file = std::fs::File::create(url).expect("create failed");
// Change the active NavLink
for i in &base.nav_elements {
if i.text == "Products" {
i.active.store(true, Ordering::Relaxed);
} else {
i.active.store(false, Ordering::Relaxed);
}
}
// Create the context
let mut context = Context::new();
context.insert("title", &base.config.title);
context.insert("description", &base.config.description);
// Fill out product information
context.insert("full_name", &obj.product.full_name);
context.insert("short_name", &obj.product.short_name);
context.insert("price_usd", &obj.product.price_usd);
context.insert("stock", &obj.product.stock);
// Insert navigation elements
for i in &base.nav_elements {
context.insert(&i.text.to_ascii_lowercase(), &i);
}
context.insert("product_classes", &base.product_classes);
// Write it to the file
output_file
.write_all(
base.tera
.render("products/product.html", &context)
.unwrap()
.as_bytes(),
)
.expect("write failed");
}
fn render_page(data: RenderObject, nav_elements: &[NavLink], product_classes: &[NavLink]) {
@ -230,15 +304,17 @@ fn render_page(data: RenderObject, nav_elements: &[NavLink], product_classes: &[
Some(p) => {
let mut estrogens = Vec::new();
let mut anti_androgens = Vec::new();
let mut progestogens = Vec::new();
for i in p {
match i.class {
ProductClass::Estrogens => estrogens.push(i),
ProductClass::AntiAndrogens => anti_androgens.push(i),
ProductClass::Progestogens => todo!(),
ProductClass::Progestogens => progestogens.push(i),
}
}
context.insert("estrogens", &estrogens);
context.insert("anti_androgens", &anti_androgens);
context.insert("progestogens", &progestogens);
}
}

View File

@ -73,4 +73,18 @@
{% endfor %}
</div>
{% endif %}
{% if progestogens %}
<h3 id="progestogens">Progestogens</h3>
<div class="images">
{% for product in progestogens %}
<div class="gallery">
<a target="_blank" href="/products/{{product.short_name}}">
<img src="/assets/products/{{product.short_name}}.webp" width="600" height="400">
</a>
<div class="desc">{{ product.full_name }}</div>
</div>
{% endfor %}
</div>
{% endif %}
</html>

View File

@ -0,0 +1,55 @@
<!DOCTYPE html>
<html lang="en">
<head>
{# head content #}
<title>{{ title }}</title>
<!-- css style -->
<link rel="preload" href="/style.css" as="style">
<link rel="stylesheet" href="/style.css">
<!-- metadata -->
<meta charset="UTF-8">
<meta name="description" content="{{ description }}">
<!-- link preview card -->
<meta name="og:title" content="{{ title }}">
<meta name="twitter:title" content="{{ title }}">
<meta name="og:description" content="{{ description }}">
<meta name="twitter:description" content="{{ description }}">
<!-- display settings & favicon -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="/assets/pill.svg">
</head>
<body>
<h1>{{ title }}</h1>
{# Navigation #}
<nav class="menu">
<ul>
<li><a {% if home.active %}class="active"{% endif %} href="{{ home.href }}">{{ home.text }}</a></li>
<li><a {% if about.active %}class="active"{% endif %} href="{{ about.href }}">{{ about.text }}</a></li>
<li class="dropdown">
<a {% if products.active %}class="active"{% endif %} href="{{ products.href }}" class="dropbtn">{{ products.text }}</a>
<div class="dropdown-content">
{% for product in product_classes %}
<a href="{{product.href}}">{{product.text}}</a>
{% endfor %}
</div>
</li>
<li><a {% if contact.active %}class="active"{% endif %} href="{{ contact.href }}">{{ contact.text }}</a></li>
</ul>
</nav>
<h2 class="product">{{ full_name }}</h2>
<img class="product" alt="{{full_name}}" src="/assets/products/{{short_name}}.webp">
<div class="product">
<code class="price" id="price">${{price_usd}} USD</code>
<br>
<code class="price" id="stock">{{stock}} in stock</code>
</html>