login implemented
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ rust_dist
|
||||
deno_dist
|
||||
node_modules
|
||||
.env
|
||||
mize_config.toml
|
||||
|
||||
# I put testing stuff for this projekt that I don't want to push to github into a folder called gitignore
|
||||
gitignore
|
||||
|
||||
1425
Cargo.lock
generated
1425
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,5 @@ members = [
|
||||
"packages/marts",
|
||||
"packages/mize",
|
||||
"packages/ppc",
|
||||
"test", "test",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -12,7 +12,7 @@ mize = { path = "../mize/", default-features = false }
|
||||
tokio = "1.49.0"
|
||||
# https://github.com/rscarson/rustyscript/pull/415
|
||||
rustyscript = { git = 'https://github.com/rscarson/rustyscript', rev = 'e39529eac2c5d37cfcbb7d7d63da7264d7a5afdb', optional = true }
|
||||
deno_core = { version = "0.380.1", optional = true }
|
||||
#deno_core = { version = "0.380.1", optional = true }
|
||||
chrono = "0.4.44"
|
||||
reqwest = { version = "0.13.2", features = ["blocking", "json"] }
|
||||
serde = "1.0.228"
|
||||
@@ -21,7 +21,7 @@ rusqlite = { version = "0.31", features = ["bundled"], optional = true }
|
||||
|
||||
[features]
|
||||
default = ["target-os"]
|
||||
target-os = [ "clap", "mize/target-os", "deno_core", "rustyscript", "rusqlite" ]
|
||||
target-os = [ "clap", "mize/target-os", "rustyscript", "rusqlite" ]
|
||||
target-wasm = [ "mize/target-wasm" ]
|
||||
clap = ["dep:clap"]
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use mize::{mize_err, mize_part, Mize, MizeError, MizePart, MizeResult};
|
||||
use reqwest::{blocking::Client, blocking::RequestBuilder, blocking::Response, header, Method};
|
||||
use reqwest::{blocking::Client, blocking::Response, header, Method};
|
||||
use serde_json::{json, Value};
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
use mize::MizeResult;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
@@ -15,5 +16,7 @@ pub mod c2vi;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use c2vi::*;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub mod js;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use js::*;
|
||||
|
||||
@@ -267,6 +267,10 @@ rec {
|
||||
################# dev Shels ###################
|
||||
|
||||
mkMizeRustShell = attrs: mkMizeModuleShell (attrs // {
|
||||
shellHook = ''
|
||||
mize_project_root=$(${lib.getExe pkgs.git} rev-parse --show-toplevel)
|
||||
export MIZE_CONFIG_FILES=$mize_project_root/mize_config.toml
|
||||
'';
|
||||
#_shell_type = "rust";
|
||||
nativeBuildInputs = attrs.nativeBuildInputs or [] ++ [
|
||||
pkgs.wasm-pack
|
||||
@@ -274,6 +278,10 @@ rec {
|
||||
pkgs.spacetimedb
|
||||
pkgs.deno
|
||||
pkgs.pkg-config
|
||||
pkgs.gobject-introspection
|
||||
pkgs.gobject-introspection.dev
|
||||
|
||||
# thanks to: https://github.com/NixOS/nixpkgs/pull/496279/changes
|
||||
(pkgs.buildWasmBindgenCli rec {
|
||||
src = pkgs.fetchCrate {
|
||||
pname = "wasm-bindgen-cli";
|
||||
@@ -307,8 +315,38 @@ rec {
|
||||
})
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
pkgsCross.openssl
|
||||
buildInputs = with pkgs; [
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk3
|
||||
harfbuzz
|
||||
librsvg
|
||||
libsoup_3
|
||||
pango
|
||||
webkitgtk_4_1
|
||||
openssl
|
||||
wasm-bindgen-cli
|
||||
lld_20
|
||||
xdotool
|
||||
];
|
||||
|
||||
LD_LIBRARY_PATH = with pkgs; pkgs.lib.makeLibraryPath [
|
||||
xdotool
|
||||
webkitgtk_4_1
|
||||
pango
|
||||
openssl
|
||||
at-spi2-atk
|
||||
atkmm
|
||||
cairo
|
||||
gdk-pixbuf
|
||||
glib
|
||||
gtk3
|
||||
harfbuzz
|
||||
librsvg
|
||||
libsoup_3
|
||||
];
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
use crate::mize_err;
|
||||
use crate::MizeResult;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use crate::item::ItemData;
|
||||
use crate::Mize;
|
||||
use crate::MizeError;
|
||||
|
||||
type ConfigThunk = Box<dyn Fn() -> ItemData + Send + Sync>;
|
||||
|
||||
@@ -42,5 +45,34 @@ pub fn gather_config(mize: &mut Mize) -> MizeResult<()> {
|
||||
part.as_deref().unwrap().opts(&mut mize.clone());
|
||||
}
|
||||
|
||||
// populate values from config files
|
||||
let config_file_paths = env::var("MIZE_CONFIG_FILES")?;
|
||||
for config_file_path in config_file_paths.split(":") {
|
||||
println!("reading config file: {config_file_path}");
|
||||
let content = std::fs::read_to_string(config_file_path)?;
|
||||
let mut data = ItemData::from_toml(content.as_str())?;
|
||||
println!("config data: {data}");
|
||||
for path in data.get_paths_recursive()? {
|
||||
let conf_name = path.replace("/", ".");
|
||||
println!("adding config {conf_name} from config file {config_file_path}");
|
||||
let val = data.get_path(path.split("/").collect::<Vec<&str>>())?;
|
||||
match config_opts.get_mut(&conf_name) {
|
||||
Some(opt) => {
|
||||
opt.val = Some(val);
|
||||
}
|
||||
None => {
|
||||
config_opts.insert(
|
||||
conf_name.clone(),
|
||||
ConfigOpt {
|
||||
name: conf_name,
|
||||
val: Some(val),
|
||||
thunk: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ impl Mize {
|
||||
|
||||
let mut instance = Mize::empty()?;
|
||||
|
||||
instance.init();
|
||||
instance.init()?;
|
||||
|
||||
debug!(
|
||||
"instance inited with config: {}",
|
||||
@@ -868,6 +868,7 @@ impl Mize {
|
||||
|
||||
// get the cached value
|
||||
if let Some(val) = opt.val.clone() {
|
||||
println!("get_config: {name} has cached value: {val}");
|
||||
return Ok(val);
|
||||
}
|
||||
|
||||
@@ -886,12 +887,17 @@ impl Mize {
|
||||
|
||||
pub fn new_opt(&mut self, name: &str) -> ConfigOptNameAndMize {
|
||||
let mut config_opts = self.config_opts.lock().unwrap();
|
||||
let opt = ConfigOpt {
|
||||
name: name.to_owned(),
|
||||
val: None,
|
||||
thunk: None,
|
||||
};
|
||||
config_opts.insert(name.to_string(), opt);
|
||||
match config_opts.get_mut(name) {
|
||||
Some(opt) => {}
|
||||
None => {
|
||||
let opt = ConfigOpt {
|
||||
name: name.to_owned(),
|
||||
val: None,
|
||||
thunk: None,
|
||||
};
|
||||
config_opts.insert(name.to_string(), opt);
|
||||
}
|
||||
}
|
||||
ConfigOptNameAndMize {
|
||||
name: name.to_string(),
|
||||
mize: self.clone(),
|
||||
|
||||
@@ -286,6 +286,46 @@ impl ItemData {
|
||||
.to_owned()
|
||||
.into_item_data())
|
||||
}
|
||||
|
||||
pub fn get_paths_recursive(&mut self) -> MizeResult<Vec<String>> {
|
||||
let mut paths = Vec::new();
|
||||
let mut path_accu = Vec::new();
|
||||
get_paths(&mut self.0, &mut paths, &mut path_accu, true)?;
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
pub fn get_paths(&mut self) -> MizeResult<Vec<String>> {
|
||||
let mut paths = Vec::new();
|
||||
let mut path_accu = Vec::new();
|
||||
get_paths(&mut self.0, &mut paths, &mut path_accu, false)?;
|
||||
Ok(paths)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_paths(
|
||||
val: &mut CborValue,
|
||||
paths: &mut Vec<String>,
|
||||
path_accu: &mut Vec<String>,
|
||||
recurse: bool,
|
||||
) -> MizeResult<()> {
|
||||
let map = val
|
||||
.as_map_mut()
|
||||
.ok_or(mize_err!("ItemData is not map... when calling get_paths"))?;
|
||||
for (key, val) in map {
|
||||
let text = key
|
||||
.as_text()
|
||||
.ok_or(mize_err!("ItemData::get_paths but key is not of type text"))?
|
||||
.to_owned();
|
||||
if recurse && val.is_map() {
|
||||
let mut path_accu = path_accu.clone();
|
||||
path_accu.push(text);
|
||||
get_paths(val, paths, &mut path_accu, recurse)?;
|
||||
} else {
|
||||
let path = path_accu.join("/") + "/" + text.as_str();
|
||||
paths.push(path);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Default for ItemData {
|
||||
|
||||
10223
packages/ppc/Cargo.lock
generated
Normal file
10223
packages/ppc/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,26 @@ clap = { version = "4.5.59", optional = true }
|
||||
js-sys = { version = "0.3.85", optional = true }
|
||||
wasm-bindgen = { version = "0.2.108", optional = true }
|
||||
|
||||
# server deps
|
||||
axum = { version = "0.8.0", optional = true }
|
||||
axum-extra = { version = "0.10.3", features = ["cookie-private"], optional = true }
|
||||
openidconnect = { version = "4", features = ["reqwest", "rustls-tls"], optional = true }
|
||||
serde = { version = "1.0", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1.0", optional = true }
|
||||
reqwest = { version = "0.11", features = ["rustls-tls"], optional = true }
|
||||
rand = { version = "0.8", optional = true }
|
||||
url = { version = "2", optional = true }
|
||||
time = { version = "0.3", optional = true }
|
||||
tokio = { version = "1.0", features = ["full"], optional = true }
|
||||
dioxus = { version = "0.7.1", features = ["router", "fullstack", "web", "desktop", "mobile", "server", "ssr"], optional = true }
|
||||
dioxus-ssr = { version = "0.5", optional = true }
|
||||
html-escape = { version = "0.2.13", optional = true }
|
||||
axum-oidc = { version = "1.0.0-dev-1", optional = true }
|
||||
tracing = { version = "0.1.44", optional = true }
|
||||
tower = { version = "0.5", optional = true }
|
||||
tower-sessions = { version = "0.14.0", optional = true }
|
||||
tower-http = { version = "0.6.8", features = ["trace"] }
|
||||
|
||||
[[bin]]
|
||||
name = "ppc"
|
||||
path = "platform/os/main.rs"
|
||||
@@ -21,8 +41,10 @@ name = "ppc"
|
||||
|
||||
[features]
|
||||
default = ["target-os"]
|
||||
target-os = [ "clap", "mize/target-os", "marts/target-os" ]
|
||||
target-os = [ "clap", "mize/target-os", "marts/target-os", "server", "tracing" ]
|
||||
target-obsidian = [ "js-sys", "wasm-bindgen", "mize/target-wasm", "marts/target-wasm" ]
|
||||
server = [ "axum", "axum-extra", "openidconnect", "serde", "serde_json", "reqwest", "rand", "url", "time", "tokio", "dioxus", "dioxus-ssr", "html-escape", "axum-oidc", "tower", "tower-sessions" ]
|
||||
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
|
||||
@@ -1,4 +1,18 @@
|
||||
mod platform {
|
||||
#[cfg(feature = "target-obsidian")]
|
||||
mod obsidian;
|
||||
pub mod obsidian;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub mod os {
|
||||
pub mod server;
|
||||
pub mod ui;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
mod website;
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use platform::os::server::server;
|
||||
#[cfg(feature = "target-os")]
|
||||
pub use platform::os::ui;
|
||||
|
||||
@@ -5,12 +5,14 @@ use clap::crate_version;
|
||||
use mize::{Mize, MizeResult};
|
||||
|
||||
fn main() {
|
||||
let mut mize = Mize::new().expect("failed to create mize instance");
|
||||
let mut mize = Mize::new().expect("failed to create mize");
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
os_main(&mut mize);
|
||||
let result = os_main(&mut mize);
|
||||
|
||||
mize.run();
|
||||
if let Err(err) = result {
|
||||
println!("EER: {:?}", err)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "target-os")]
|
||||
@@ -19,6 +21,7 @@ fn os_main(mize: &mut Mize) -> MizeResult<()> {
|
||||
//marts::js(mize)?;
|
||||
marts::habitica(mize)?;
|
||||
marts::c2vi(mize)?;
|
||||
ppc::server(mize)?;
|
||||
|
||||
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
|
||||
|
||||
@@ -34,5 +37,12 @@ fn os_main(mize: &mut Mize) -> MizeResult<()> {
|
||||
println!("test ppc...");
|
||||
Ok(())
|
||||
});
|
||||
Ok(())
|
||||
|
||||
cli.subcommand(Command::new("gui"), |_, _| {
|
||||
ppc::ui::launch_desktop_app();
|
||||
Ok(())
|
||||
});
|
||||
|
||||
drop(cli);
|
||||
mize.run()
|
||||
}
|
||||
|
||||
138
packages/ppc/platform/os/server.rs
Normal file
138
packages/ppc/platform/os/server.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use axum::{
|
||||
Router,
|
||||
error_handling::HandleErrorLayer,
|
||||
http::Uri,
|
||||
response::IntoResponse,
|
||||
routing::{any, get},
|
||||
};
|
||||
use axum_oidc::handle_oidc_redirect;
|
||||
use axum_oidc::{
|
||||
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer,
|
||||
OidcRpInitiatedLogout, error::MiddlewareError,
|
||||
};
|
||||
use clap::Command;
|
||||
use dioxus::prelude::*;
|
||||
use mize::Mize;
|
||||
use mize::MizeResult;
|
||||
use openidconnect::Scope;
|
||||
use openidconnect::{ClientId, ClientSecret, IssuerUrl};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_sessions::{
|
||||
Expiry, MemoryStore, SessionManagerLayer,
|
||||
cookie::{SameSite, time::Duration},
|
||||
};
|
||||
|
||||
pub fn server(mize: &mut Mize) -> MizeResult<()> {
|
||||
let mut cli = mize.get_part_native::<marts::CliPart>("cli")?;
|
||||
mize.add_name_only_part("ppc.server");
|
||||
|
||||
mize.new_opt("auth.issuer");
|
||||
mize.new_opt("auth.client_id");
|
||||
mize.new_opt("auth.client_secret");
|
||||
mize.new_opt("auth.redirect");
|
||||
mize.new_opt("auth.cookie_key");
|
||||
|
||||
let mut mize = mize.clone();
|
||||
cli.subcommand(Command::new("server"), move |_, _| {
|
||||
// Create tokio runtime for async server
|
||||
let rt = tokio::runtime::Runtime::new()?;
|
||||
rt.block_on(async { start_server(&mut mize).await })?;
|
||||
|
||||
Ok(())
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn start_server(mize: &mut Mize) -> MizeResult<()> {
|
||||
dioxus::logger::initialize_default();
|
||||
|
||||
let issuer = mize.get_config("auth.issuer")?.value_string()?;
|
||||
let client_id = mize.get_config("auth.client_id")?.value_string()?;
|
||||
let client_secret = mize.get_config("auth.client_secret")?.value_string()?;
|
||||
let url = mize.get_config("web.url")?.value_string()?;
|
||||
let redirect_url = format!("{url}/oidc");
|
||||
|
||||
let session_store = MemoryStore::default();
|
||||
let session_layer = SessionManagerLayer::new(session_store)
|
||||
.with_secure(false)
|
||||
.with_same_site(SameSite::Lax)
|
||||
.with_expiry(Expiry::OnInactivity(Duration::seconds(120)));
|
||||
|
||||
let oidc_login_service = ServiceBuilder::new()
|
||||
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
||||
dbg!(&e);
|
||||
e.into_response()
|
||||
}))
|
||||
.layer(OidcLoginLayer::<EmptyAdditionalClaims>::new());
|
||||
|
||||
let oidc_client = OidcClient::<EmptyAdditionalClaims>::builder()
|
||||
.with_default_http_client()
|
||||
.with_redirect_url(Uri::from_str(redirect_url.as_str())?)
|
||||
.with_client_id(ClientId::new(client_id))
|
||||
.add_scope(Scope::new("profile".into()))
|
||||
.add_scope(Scope::new("email".into()))
|
||||
// Optional: add untrusted audiences. If the `aud` claim contains any of these audiences, the token is rejected.
|
||||
//.add_untrusted_audience(Audience::new("123456789".to_string()))
|
||||
.with_client_secret(ClientSecret::new(client_secret))
|
||||
.discover(IssuerUrl::new(issuer.into()).expect("Invalid IssuerUrl"))
|
||||
.await
|
||||
.unwrap()
|
||||
.build();
|
||||
|
||||
let oidc_auth_service = ServiceBuilder::new()
|
||||
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
|
||||
dbg!(&e);
|
||||
e.into_response()
|
||||
}))
|
||||
.layer(OidcAuthLayer::new(oidc_client));
|
||||
|
||||
let app = Router::new()
|
||||
.route("/foo", get(authenticated))
|
||||
.route("/logout", get(logout))
|
||||
.layer(oidc_login_service)
|
||||
.route("/bar", get(maybe_authenticated))
|
||||
.route("/oidc", any(handle_oidc_redirect::<EmptyAdditionalClaims>))
|
||||
.layer(oidc_auth_service)
|
||||
.layer(session_layer)
|
||||
.layer(tower_http::trace::TraceLayer::new_for_http())
|
||||
// not authenticated routes
|
||||
.route("/", get(ppc_main_page));
|
||||
|
||||
tracing::info!("Running on http://localhost:3000");
|
||||
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
axum::serve(listener, app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn authenticated(claims: OidcClaims<EmptyAdditionalClaims>) -> impl IntoResponse {
|
||||
format!("Hello {}", claims.subject().as_str())
|
||||
}
|
||||
|
||||
async fn ppc_main_page() -> impl IntoResponse {
|
||||
"hi"
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
async fn maybe_authenticated(
|
||||
claims: Result<OidcClaims<EmptyAdditionalClaims>, axum_oidc::error::ExtractorError>,
|
||||
) -> impl IntoResponse {
|
||||
if let Ok(claims) = claims {
|
||||
format!(
|
||||
"Hello {}! You are already logged in from another Handler.",
|
||||
claims.subject().as_str()
|
||||
)
|
||||
} else {
|
||||
"Hello anon!".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
async fn logout(logout: OidcRpInitiatedLogout) -> impl IntoResponse {
|
||||
logout.with_post_logout_redirect(Uri::from_static("https://localhost:3000"))
|
||||
}
|
||||
17
packages/ppc/platform/os/ui/mod.rs
Normal file
17
packages/ppc/platform/os/ui/mod.rs
Normal file
@@ -0,0 +1,17 @@
|
||||
// Minimal cross-platform UI component
|
||||
use dioxus::prelude::*;
|
||||
|
||||
pub fn hello_world() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
style: "text-align: center; padding: 2rem;",
|
||||
h1 { "Hello from Mize!" }
|
||||
p { "This UI works on Web, Desktop, and Obsidian plugin" }
|
||||
p { "Current platform: {std::env::consts::OS}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn launch_desktop_app() {
|
||||
dioxus::launch(hello_world);
|
||||
}
|
||||
185
packages/ppc/website/mod.rs
Normal file
185
packages/ppc/website/mod.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
// PPC Website UI Module
|
||||
// This module contains the user interface components for the PPC website
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
||||
/// Main PPC website component
|
||||
|
||||
pub fn ppc_website() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "min-h-screen bg-gray-100",
|
||||
|
||||
// Header
|
||||
header {
|
||||
class: "bg-blue-600 text-white py-4",
|
||||
div {
|
||||
class: "container mx-auto px-4",
|
||||
h1 {
|
||||
class: "text-2xl font-bold",
|
||||
"People Corner (PPC)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main content
|
||||
main {
|
||||
class: "container mx-auto px-4 py-8",
|
||||
|
||||
// Hero section
|
||||
section {
|
||||
class: "bg-white rounded-lg shadow-md p-6 mb-8",
|
||||
h2 {
|
||||
class: "text-xl font-semibold mb-4 text-blue-600",
|
||||
"Welcome to People Corner"
|
||||
}
|
||||
p {
|
||||
class: "text-gray-700 mb-4",
|
||||
"PPC is a social platform for intentional people who want to get more out of life together."
|
||||
}
|
||||
p {
|
||||
class: "text-gray-700",
|
||||
"We're building a community-driven social media network that brings people together to do cool things, share resources, and support each other."
|
||||
}
|
||||
}
|
||||
|
||||
// Mission section
|
||||
section {
|
||||
class: "bg-white rounded-lg shadow-md p-6 mb-8",
|
||||
h2 {
|
||||
class: "text-xl font-semibold mb-4 text-blue-600",
|
||||
"Our Mission"
|
||||
}
|
||||
ul {
|
||||
class: "list-disc pl-6 space-y-2",
|
||||
li { "Bring people together to get more out of life" }
|
||||
li { "Create meaningful connections beyond superficial social media" }
|
||||
li { "Support intentional living and personal growth" }
|
||||
li { "Build a platform by people, for people" }
|
||||
li { "Foster real-world meetups and collaborations" }
|
||||
li { "Create a social media for diligent, intentional people" }
|
||||
}
|
||||
}
|
||||
|
||||
// Key concepts section
|
||||
section {
|
||||
class: "bg-white rounded-lg shadow-md p-6",
|
||||
h2 {
|
||||
class: "text-xl font-semibold mb-4 text-blue-600",
|
||||
"Key Concepts"
|
||||
}
|
||||
ul {
|
||||
class: "list-disc pl-6 space-y-2",
|
||||
li {
|
||||
strong { "Trust Network: " }
|
||||
"A system for building and maintaining trust between members"
|
||||
}
|
||||
li {
|
||||
strong { "Intentional Communities: " }
|
||||
"Both online and physical spaces for like-minded people"
|
||||
}
|
||||
li {
|
||||
strong { "Resource Sharing: " }
|
||||
"Platform for sharing skills, knowledge, and physical resources"
|
||||
}
|
||||
li {
|
||||
strong { "Real Connections: " }
|
||||
"Focus on meaningful relationships over superficial interactions"
|
||||
}
|
||||
li {
|
||||
strong { "Personal Growth: " }
|
||||
"Support for individuals to achieve their life goals"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
footer {
|
||||
class: "bg-blue-600 text-white py-4 mt-8",
|
||||
div {
|
||||
class: "container mx-auto px-4 text-center",
|
||||
p { "© 2024 People Corner. All rights reserved." }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// About page component
|
||||
pub fn about_page() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "container mx-auto px-4 py-8",
|
||||
h1 {
|
||||
class: "text-2xl font-bold mb-4 text-blue-600",
|
||||
"About PPC"
|
||||
}
|
||||
p {
|
||||
class: "text-gray-700 mb-4",
|
||||
"People Corner (PPC) is more than just a social platform - it's a movement to bring intentional people together to create meaningful connections and achieve more in life."
|
||||
}
|
||||
p {
|
||||
class: "text-gray-700",
|
||||
"Our vision is to build a world where people support each other, share resources, and grow together through real-world interactions and digital connections."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contact page component
|
||||
pub fn contact_page() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
class: "container mx-auto px-4 py-8",
|
||||
h1 {
|
||||
class: "text-2xl font-bold mb-4 text-blue-600",
|
||||
"Contact Us"
|
||||
}
|
||||
p {
|
||||
class: "text-gray-700 mb-4",
|
||||
"We'd love to hear from you! Whether you have questions, feedback, or want to get involved, please reach out."
|
||||
}
|
||||
form {
|
||||
class: "max-w-md space-y-4",
|
||||
div {
|
||||
label {
|
||||
class: "block text-gray-700 mb-2",
|
||||
"Name"
|
||||
}
|
||||
input {
|
||||
class: "w-full px-3 py-2 border rounded-lg",
|
||||
r#type: "text",
|
||||
placeholder: "Your name"
|
||||
}
|
||||
}
|
||||
div {
|
||||
label {
|
||||
class: "block text-gray-700 mb-2",
|
||||
"Email"
|
||||
}
|
||||
input {
|
||||
class: "w-full px-3 py-2 border rounded-lg",
|
||||
r#type: "email",
|
||||
placeholder: "Your email"
|
||||
}
|
||||
}
|
||||
div {
|
||||
label {
|
||||
class: "block text-gray-700 mb-2",
|
||||
"Message"
|
||||
}
|
||||
textarea {
|
||||
class: "w-full px-3 py-2 border rounded-lg h-32",
|
||||
placeholder: "Your message"
|
||||
}
|
||||
}
|
||||
button {
|
||||
class: "bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700",
|
||||
r#type: "submit",
|
||||
"Send Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user