use crate::schema::*;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum UiTemplate {
Registration,
Login,
UserProfile,
Settings,
ConfirmDelete,
StatusDashboard,
DataTable,
SuccessMessage,
ErrorMessage,
Loading,
}
impl UiTemplate {
pub fn all_names() -> &'static [&'static str] {
&[
"registration",
"login",
"user_profile",
"settings",
"confirm_delete",
"status_dashboard",
"data_table",
"success_message",
"error_message",
"loading",
]
}
pub fn from_name(name: &str) -> Option<Self> {
match name.to_lowercase().as_str() {
"registration" | "register" | "signup" => Some(Self::Registration),
"login" | "signin" => Some(Self::Login),
"user_profile" | "profile" => Some(Self::UserProfile),
"settings" | "preferences" => Some(Self::Settings),
"confirm_delete" | "delete_confirm" => Some(Self::ConfirmDelete),
"status_dashboard" | "dashboard" | "status" => Some(Self::StatusDashboard),
"data_table" | "table" => Some(Self::DataTable),
"success_message" | "success" => Some(Self::SuccessMessage),
"error_message" | "error" => Some(Self::ErrorMessage),
"loading" | "spinner" => Some(Self::Loading),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TemplateData {
pub title: Option<String>,
pub description: Option<String>,
pub user: Option<UserData>,
pub data: HashMap<String, String>,
pub stats: Vec<StatItem>,
pub columns: Vec<TableColumn>,
pub rows: Vec<HashMap<String, serde_json::Value>>,
pub message: Option<String>,
pub theme: Option<Theme>,
}
#[derive(Debug, Clone)]
pub struct UserData {
pub name: String,
pub email: String,
pub avatar_url: Option<String>,
pub role: Option<String>,
}
#[derive(Debug, Clone)]
pub struct StatItem {
pub label: String,
pub value: String,
pub status: Option<String>,
}
pub fn render_template(template: UiTemplate, data: TemplateData) -> UiResponse {
let components = match template {
UiTemplate::Registration => registration_template(&data),
UiTemplate::Login => login_template(&data),
UiTemplate::UserProfile => user_profile_template(&data),
UiTemplate::Settings => settings_template(&data),
UiTemplate::ConfirmDelete => confirm_delete_template(&data),
UiTemplate::StatusDashboard => status_dashboard_template(&data),
UiTemplate::DataTable => data_table_template(&data),
UiTemplate::SuccessMessage => success_message_template(&data),
UiTemplate::ErrorMessage => error_message_template(&data),
UiTemplate::Loading => loading_template(&data),
};
let mut response = UiResponse::new(components);
if let Some(theme) = data.theme {
response = response.with_theme(theme);
}
response
}
fn registration_template(data: &TemplateData) -> Vec<Component> {
vec![Component::Card(Card {
id: Some("registration-card".to_string()),
title: Some(
data.title
.clone()
.unwrap_or_else(|| "Create Account".to_string()),
),
description: data
.description
.clone()
.or_else(|| Some("Enter your details to register".to_string())),
content: vec![
Component::TextInput(TextInput {
id: Some("name".to_string()),
name: "name".to_string(),
label: "Full Name".to_string(),
placeholder: Some("Enter your name".to_string()),
input_type: "text".to_string(),
required: true,
default_value: None,
error: None,
min_length: Some(2),
max_length: Some(100),
}),
Component::TextInput(TextInput {
id: Some("email".to_string()),
name: "email".to_string(),
label: "Email".to_string(),
placeholder: Some("you@example.com".to_string()),
input_type: "email".to_string(),
required: true,
default_value: None,
error: None,
min_length: None,
max_length: None,
}),
Component::TextInput(TextInput {
id: Some("password".to_string()),
name: "password".to_string(),
label: "Password".to_string(),
placeholder: Some("Choose a strong password".to_string()),
input_type: "password".to_string(),
required: true,
default_value: None,
error: None,
min_length: Some(8),
max_length: None,
}),
],
footer: Some(vec![Component::Button(Button {
id: Some("submit".to_string()),
label: "Create Account".to_string(),
action_id: "register_submit".to_string(),
variant: ButtonVariant::Primary,
disabled: false,
icon: None,
})]),
})]
}
fn login_template(data: &TemplateData) -> Vec<Component> {
vec![Component::Card(Card {
id: Some("login-card".to_string()),
title: Some(
data.title
.clone()
.unwrap_or_else(|| "Welcome Back".to_string()),
),
description: data
.description
.clone()
.or_else(|| Some("Sign in to your account".to_string())),
content: vec![
Component::TextInput(TextInput {
id: Some("email".to_string()),
name: "email".to_string(),
label: "Email".to_string(),
placeholder: Some("you@example.com".to_string()),
input_type: "email".to_string(),
required: true,
default_value: None,
error: None,
min_length: None,
max_length: None,
}),
Component::TextInput(TextInput {
id: Some("password".to_string()),
name: "password".to_string(),
label: "Password".to_string(),
placeholder: Some("Enter your password".to_string()),
input_type: "password".to_string(),
required: true,
default_value: None,
error: None,
min_length: None,
max_length: None,
}),
],
footer: Some(vec![Component::Button(Button {
id: Some("submit".to_string()),
label: "Sign In".to_string(),
action_id: "login_submit".to_string(),
variant: ButtonVariant::Primary,
disabled: false,
icon: None,
})]),
})]
}
fn user_profile_template(data: &TemplateData) -> Vec<Component> {
let user = data.user.as_ref();
let name = user
.map(|u| u.name.clone())
.unwrap_or_else(|| "User".to_string());
let email = user
.map(|u| u.email.clone())
.unwrap_or_else(|| "user@example.com".to_string());
let role = user
.and_then(|u| u.role.clone())
.unwrap_or_else(|| "Member".to_string());
vec![Component::Card(Card {
id: Some("profile-card".to_string()),
title: Some(
data.title
.clone()
.unwrap_or_else(|| "User Profile".to_string()),
),
description: None,
content: vec![
Component::Text(Text {
id: None,
content: format!("**{}**", name),
variant: TextVariant::H3,
}),
Component::Badge(Badge {
id: None,
label: role,
variant: BadgeVariant::Info,
}),
Component::Divider(Divider { id: None }),
Component::KeyValue(KeyValue {
id: None,
pairs: vec![KeyValuePair {
key: "Email".to_string(),
value: email,
}],
}),
],
footer: Some(vec![Component::Button(Button {
id: Some("edit".to_string()),
label: "Edit Profile".to_string(),
action_id: "edit_profile".to_string(),
variant: ButtonVariant::Secondary,
disabled: false,
icon: None,
})]),
})]
}
fn settings_template(data: &TemplateData) -> Vec<Component> {
vec![Component::Card(Card {
id: Some("settings-card".to_string()),
title: Some(data.title.clone().unwrap_or_else(|| "Settings".to_string())),
description: data
.description
.clone()
.or_else(|| Some("Manage your preferences".to_string())),
content: vec![
Component::Switch(Switch {
id: Some("notifications".to_string()),
name: "notifications".to_string(),
label: "Email Notifications".to_string(),
default_checked: true,
}),
Component::Switch(Switch {
id: Some("dark_mode".to_string()),
name: "dark_mode".to_string(),
label: "Dark Mode".to_string(),
default_checked: false,
}),
Component::Select(Select {
id: Some("language".to_string()),
name: "language".to_string(),
label: "Language".to_string(),
options: vec![
SelectOption {
value: "en".to_string(),
label: "English".to_string(),
},
SelectOption {
value: "es".to_string(),
label: "Spanish".to_string(),
},
SelectOption {
value: "fr".to_string(),
label: "French".to_string(),
},
],
required: false,
error: None,
}),
],
footer: Some(vec![Component::Button(Button {
id: Some("save".to_string()),
label: "Save Settings".to_string(),
action_id: "save_settings".to_string(),
variant: ButtonVariant::Primary,
disabled: false,
icon: None,
})]),
})]
}
fn confirm_delete_template(data: &TemplateData) -> Vec<Component> {
vec![Component::Modal(Modal {
id: Some("confirm-delete-modal".to_string()),
title: data
.title
.clone()
.unwrap_or_else(|| "Confirm Deletion".to_string()),
content: vec![Component::Alert(Alert {
id: None,
title: "Warning".to_string(),
description: Some(data.message.clone().unwrap_or_else(|| {
"This action cannot be undone. All data will be permanently deleted.".to_string()
})),
variant: AlertVariant::Warning,
})],
footer: Some(vec![
Component::Button(Button {
id: Some("cancel".to_string()),
label: "Cancel".to_string(),
action_id: "cancel_delete".to_string(),
variant: ButtonVariant::Secondary,
disabled: false,
icon: None,
}),
Component::Button(Button {
id: Some("confirm".to_string()),
label: "Delete".to_string(),
action_id: "confirm_delete".to_string(),
variant: ButtonVariant::Danger,
disabled: false,
icon: None,
}),
]),
size: ModalSize::Small,
closable: true,
})]
}
fn status_dashboard_template(data: &TemplateData) -> Vec<Component> {
let stats = if data.stats.is_empty() {
vec![
StatItem {
label: "CPU".to_string(),
value: "45%".to_string(),
status: Some("ok".to_string()),
},
StatItem {
label: "Memory".to_string(),
value: "78%".to_string(),
status: Some("warning".to_string()),
},
StatItem {
label: "Disk".to_string(),
value: "32%".to_string(),
status: Some("ok".to_string()),
},
]
} else {
data.stats.clone()
};
vec![
Component::Text(Text {
id: None,
content: data
.title
.clone()
.unwrap_or_else(|| "System Status".to_string()),
variant: TextVariant::H2,
}),
Component::Grid(Grid {
id: None,
columns: stats.len().min(4) as u8,
gap: 4,
children: stats
.iter()
.map(|stat| {
let status_variant = match stat.status.as_deref() {
Some("ok") | Some("success") => BadgeVariant::Success,
Some("warning") => BadgeVariant::Warning,
Some("error") | Some("critical") => BadgeVariant::Error,
_ => BadgeVariant::Default,
};
Component::Card(Card {
id: None,
title: None,
description: None,
content: vec![
Component::Text(Text {
id: None,
content: stat.label.clone(),
variant: TextVariant::Caption,
}),
Component::Text(Text {
id: None,
content: stat.value.clone(),
variant: TextVariant::H3,
}),
Component::Badge(Badge {
id: None,
label: stat.status.clone().unwrap_or_else(|| "ok".to_string()),
variant: status_variant,
}),
],
footer: None,
})
})
.collect(),
}),
]
}
fn data_table_template(data: &TemplateData) -> Vec<Component> {
let columns = if data.columns.is_empty() {
vec![
TableColumn {
header: "ID".to_string(),
accessor_key: "id".to_string(),
sortable: true,
},
TableColumn {
header: "Name".to_string(),
accessor_key: "name".to_string(),
sortable: true,
},
TableColumn {
header: "Status".to_string(),
accessor_key: "status".to_string(),
sortable: false,
},
]
} else {
data.columns.clone()
};
vec![
Component::Text(Text {
id: None,
content: data.title.clone().unwrap_or_else(|| "Data".to_string()),
variant: TextVariant::H2,
}),
Component::Table(Table {
id: Some("data-table".to_string()),
columns,
data: data.rows.clone(),
sortable: true,
page_size: Some(10),
striped: true,
}),
]
}
fn success_message_template(data: &TemplateData) -> Vec<Component> {
vec![Component::Alert(Alert {
id: Some("success-alert".to_string()),
title: data.title.clone().unwrap_or_else(|| "Success!".to_string()),
description: data
.message
.clone()
.or_else(|| Some("Operation completed successfully.".to_string())),
variant: AlertVariant::Success,
})]
}
fn error_message_template(data: &TemplateData) -> Vec<Component> {
vec![Component::Alert(Alert {
id: Some("error-alert".to_string()),
title: data.title.clone().unwrap_or_else(|| "Error".to_string()),
description: data
.message
.clone()
.or_else(|| Some("Something went wrong. Please try again.".to_string())),
variant: AlertVariant::Error,
})]
}
fn loading_template(data: &TemplateData) -> Vec<Component> {
vec![Component::Spinner(Spinner {
id: Some("loading-spinner".to_string()),
size: SpinnerSize::Large,
label: data
.message
.clone()
.or_else(|| Some("Loading...".to_string())),
})]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_registration_template() {
let response = render_template(UiTemplate::Registration, TemplateData::default());
assert_eq!(response.components.len(), 1);
}
#[test]
fn test_template_from_name() {
assert_eq!(
UiTemplate::from_name("registration"),
Some(UiTemplate::Registration)
);
assert_eq!(
UiTemplate::from_name("signup"),
Some(UiTemplate::Registration)
);
assert_eq!(UiTemplate::from_name("login"), Some(UiTemplate::Login));
assert_eq!(UiTemplate::from_name("unknown"), None);
}
}