diff --git a/PROMPT.md b/PROMPT.md index 8fe759632..e25d2ab59 100644 --- a/PROMPT.md +++ b/PROMPT.md @@ -172,14 +172,6 @@ date.with_hour(9).and_then(|d| d.with_minute(0)).unwrap_or(date) --- -## BUILD RULES - -- Development: `cargo build` (debug only) -- NEVER run `cargo clippy` manually - use diagnostics tool -- Version: 6.1.0 - do not change - ---- - ## DATABASE STANDARDS - TABLES AND INDEXES ONLY (no views, triggers, functions) @@ -210,6 +202,34 @@ date.with_hour(9).and_then(|d| d.with_minute(0)).unwrap_or(date) --- +## COMPILATION POLICY - CRITICAL + +**NEVER compile during development. NEVER run `cargo build`. Use static analysis only.** + +### Workflow + +1. Make all code changes +2. Use `diagnostics` tool for static analysis (NOT compilation) +3. Fix any errors found by diagnostics +4. **At the end**, inform user what needs restart + +### After All Changes Complete + +| Change Type | User Action Required | +|-------------|----------------------| +| Rust code (`.rs` files) | "Recompile and restart **botserver**" | +| HTML templates (`.html` in botui) | "Browser refresh only" | +| CSS/JS files | "Browser refresh only" | +| Askama templates (`.html` in botserver) | "Recompile and restart **botserver**" | +| Database migrations | "Run migration, then restart **botserver**" | +| Cargo.toml changes | "Recompile and restart **botserver**" | + +**Format:** At the end of your response, always state: +- ✅ **No restart needed** - browser refresh only +- 🔄 **Restart botserver** - recompile required + +--- + ## KEY REMINDERS - **ZERO WARNINGS** - fix every clippy warning diff --git a/src/core/i18n.rs b/src/core/i18n.rs index f549324f2..72a137168 100644 --- a/src/core/i18n.rs +++ b/src/core/i18n.rs @@ -808,6 +808,74 @@ const TRANSLATION_KEYS: &[&str] = &[ "attendant-status-online", "attendant-select-conversation", "sources-search", + "people-title", + "people-subtitle", + "people-search", + "people-add", + "people-tab-contacts", + "people-tab-groups", + "people-tab-directory", + "people-tab-recent", + "people-loading", + "people-add-contact", + "people-first-name", + "people-last-name", + "people-email", + "people-phone", + "people-company", + "people-job-title", + "people-notes", + "people-empty-title", + "people-empty-desc", + "crm-title", + "crm-subtitle", + "crm-leads", + "crm-opportunities", + "crm-contacts", + "crm-accounts", + "crm-pipeline", + "crm-new-lead", + "crm-new-opportunity", + "crm-new-contact", + "crm-new-account", + "crm-stage-lead", + "crm-stage-qualified", + "crm-stage-proposal", + "crm-stage-negotiation", + "crm-stage-won", + "crm-stage-lost", + "crm-conversion-rate", + "crm-pipeline-value", + "crm-avg-deal", + "crm-won-month", + "billing-title", + "billing-subtitle", + "billing-invoices", + "billing-payments", + "billing-quotes", + "billing-new-invoice", + "billing-new-payment", + "billing-new-quote", + "billing-pending", + "billing-paid", + "billing-overdue", + "billing-revenue-month", + "billing-total-revenue", + "products-title", + "products-subtitle", + "products-items", + "products-services", + "products-pricelists", + "products-new-product", + "products-new-service", + "products-new-pricelist", + "products-total-products", + "products-total-services", + "products-active", + "products-inactive", + "products-sku", + "products-price", + "products-category", ]; pub fn get_translations_json(locale: &Locale) -> serde_json::Value { diff --git a/src/main.rs b/src/main.rs index 2797801d5..124e09f1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -383,6 +383,7 @@ async fn run_axum_server( api_router = api_router.merge(botserver::analytics::goals::configure_goals_routes()); api_router = api_router.merge(botserver::player::configure_player_routes()); api_router = api_router.merge(botserver::canvas::configure_canvas_routes()); + api_router = api_router.merge(botserver::social::configure_social_routes()); #[cfg(feature = "whatsapp")] { diff --git a/src/social/mod.rs b/src/social/mod.rs index 7568bb90c..67194e38e 100644 --- a/src/social/mod.rs +++ b/src/social/mod.rs @@ -1,5 +1,5 @@ use axum::{ - extract::{Path, Query, State}, + extract::{Form, Path, Query, State}, response::{Html, IntoResponse}, routing::{delete, get, post, put}, Json, Router, @@ -293,6 +293,13 @@ pub struct CreatePostRequest { pub poll: Option, } +#[derive(Debug, Deserialize)] +pub struct CreatePostForm { + pub content: String, + pub visibility: Option, + pub community_id: Option, +} + #[derive(Debug, Deserialize)] pub struct AttachmentRequest { pub file_type: AttachmentType, @@ -855,13 +862,53 @@ pub async fn handle_get_feed_html( pub async fn handle_create_post( State(_state): State>, - Json(req): Json, -) -> Result, SocialError> { + Form(form): Form, +) -> Result, SocialError> { let service = SocialService::new(); let org_id = Uuid::nil(); let user_id = Uuid::nil(); + + let visibility = form.visibility.as_deref().and_then(|v| match v { + "public" => Some(PostVisibility::Public), + "organization" => Some(PostVisibility::Organization), + "community" => Some(PostVisibility::Community), + _ => None, + }); + + let community_id = form + .community_id + .as_deref() + .filter(|s| !s.is_empty()) + .and_then(|s| Uuid::parse_str(s).ok()); + + let req = CreatePostRequest { + content: form.content, + content_type: None, + community_id, + visibility, + mentions: None, + hashtags: None, + attachments: None, + poll: None, + }; + let post = service.create_post(org_id, user_id, req).await?; - Ok(Json(post)) + + let post_with_author = PostWithAuthor { + post, + author: UserSummary { + id: user_id, + name: "You".to_string(), + avatar_url: None, + title: None, + is_leader: false, + }, + community: None, + user_reaction: None, + is_bookmarked: false, + }; + + Ok(Html(render_post_card_html(&post_with_author))) } pub async fn handle_get_post(