Skip to main content

rustbasic_breeze/
lib.rs

1use std::fs::{self, OpenOptions};
2use std::io::{Read, Write};
3use colored::*;
4use regex::Regex;
5
6pub fn update_controller_mod_rs(mod_name: &str) {
7    let mod_path = "src/app/http/controllers/mod.rs";
8    let mut content = String::new();
9    if let Ok(mut file) = fs::File::open(mod_path) {
10        file.read_to_string(&mut content).ok();
11    }
12
13    let line = format!("pub mod {};", mod_name);
14    if content.contains(&line) {
15        return;
16    }
17
18    let mut file = OpenOptions::new()
19        .append(true)
20        .open(mod_path)
21        .expect("Gagal membuka controllers/mod.rs");
22
23    writeln!(file, "{}", line).ok();
24    println!("{} {}", "📝".blue(), "controllers/mod.rs diperbarui.".dimmed());
25}
26
27pub fn update_migration_mod_rs(mod_name: &str) {
28    let mod_path = "database/migrations/mod.rs";
29    let mut content = String::new();
30    if let Ok(mut file) = fs::File::open(mod_path) {
31        file.read_to_string(&mut content).ok();
32    }
33
34    // Tambahkan mod declaration
35    if !content.contains(&format!("pub mod {};", mod_name)) {
36        if !content.ends_with('\n') {
37            content.push('\n');
38        }
39        content.push_str(&format!("pub mod {};\n", mod_name));
40    }
41
42    // Tambahkan ke list migrations
43    let search_pattern = "fn migrations() -> Vec<Box<dyn sea_orm_migration::prelude::MigrationTrait>> {";
44    let search_pattern_alt = "fn migrations() -> Vec<Box<dyn MigrationTrait>> {";
45    
46    let mut pos = content.find(search_pattern);
47    if pos.is_none() {
48        pos = content.find(search_pattern_alt);
49    }
50    
51    if let Some(_pos) = pos {
52        let insert_pos = content.find("        ]").unwrap_or(content.len());
53        content.insert_str(insert_pos, &format!("            Box::new({}::Migration),\n", mod_name));
54    }
55
56    fs::write(mod_path, content).expect("Gagal memperbarui database/migrations/mod.rs");
57    println!("{} {}", "📝".blue(), "database/migrations/mod.rs diperbarui.".dimmed());
58}
59
60pub async fn make_auth() {
61    println!("\n{}", "🔐 Scaffolding Authentication...".magenta().bold());
62
63    // Dynamically add validator and sea-orm-migration to Cargo.toml if not present
64    if let Ok(mut content) = fs::read_to_string("Cargo.toml") {
65        let mut changed = false;
66        
67        if !content.contains("validator = ") {
68            if let Some(pos) = content.find("[dependencies]") {
69                let insert_pos = pos + "[dependencies]".len();
70                content.insert_str(insert_pos, "\nvalidator = { version = \"0.20\", features = [\"derive\"] }");
71                changed = true;
72            }
73        }
74        
75        if !content.contains("sea-orm-migration = ") {
76            if let Some(pos) = content.find("[dependencies]") {
77                let insert_pos = pos + "[dependencies]".len();
78                content.insert_str(insert_pos, "\nsea-orm-migration = { version = \"1.1\", features = [\"runtime-tokio-rustls\", \"sqlx-sqlite\", \"sqlx-mysql\"], default-features = false }");
79                changed = true;
80            }
81        }
82        
83        if changed {
84            fs::write("Cargo.toml", content).ok();
85            println!("   {} {}", "📝 Updated:".blue(), "Cargo.toml dependencies".cyan());
86        }
87    }
88
89    // 1. Create src/routes/auth.rs
90    let auth_route_path = "src/routes/auth.rs";
91    let auth_route_template = r#"use rustbasic_core::axum::{Router, routing::{get, post}, middleware::from_fn};
92use crate::app::http::controllers::auth;
93use crate::app::http::middleware::auth::guest_middleware;
94use rustbasic_core::server::AppState;
95
96pub fn router() -> Router<AppState> {
97    Router::new()
98        .route("/login", get(auth::auth_controller::AuthController::login_page))
99        .route("/login", post(auth::auth_controller::AuthController::login))
100        .route("/register", get(auth::auth_controller::AuthController::register_page))
101        .route("/register", post(auth::auth_controller::AuthController::register))
102        .route("/forgot-password", get(auth::auth_controller::AuthController::forgot_password_page))
103        .route("/forgot-password", post(auth::auth_controller::AuthController::send_reset_link))
104        .route("/reset-password", get(auth::auth_controller::AuthController::reset_password_page))
105        .route("/reset-password", post(auth::auth_controller::AuthController::update_password))
106        .layer(from_fn(guest_middleware))
107}
108"#;
109    if !std::path::Path::new(auth_route_path).exists() {
110        fs::write(auth_route_path, auth_route_template).ok();
111        println!("   {} {}", "✅ Created:".green(), auth_route_path.cyan());
112    } else {
113        println!("   {} {}", "⚠️  Exists:".yellow(), auth_route_path.cyan());
114    }
115
116    // 2. Update src/routes/mod.rs
117    let routes_mod_path = "src/routes/mod.rs";
118    if let Ok(mut content) = fs::read_to_string(routes_mod_path)
119        && !content.contains("pub mod auth;") {
120            content.push_str("pub mod auth;\n");
121            fs::write(routes_mod_path, content).ok();
122            println!("   {} {}", "📝 Updated:".blue(), routes_mod_path.cyan());
123        }
124
125    // 3. Update src/routes/web.rs
126    let web_route_path = "src/routes/web.rs";
127    if let Ok(mut content) = fs::read_to_string(web_route_path)
128        && !content.contains("use crate::routes::auth as auth_routes;") {
129            content = content.replace("use rustbasic_core::axum::{Router, routing::get};", "use rustbasic_core::axum::{Router, routing::{get, post}, middleware::from_fn};");
130            content = content.replace("use rustbasic_core::server::AppState;", "use crate::app::http::controllers::{auth, dashboard_controller};\nuse crate::app::http::middleware::auth::auth_middleware;\nuse rustbasic_core::server::AppState;\nuse crate::routes::auth as auth_routes;");
131
132            let merge_logic = r#"let auth_protected_routes = Router::new()
133        .route("/dashboard", get(dashboard_controller::DashboardController::index))
134        .route("/logout", post(auth::auth_controller::AuthController::logout))
135        .layer(from_fn(auth_middleware));
136
137    Router::new()
138        .route("/", get(welcome_controller::index))
139        .route("/about", get(welcome_controller::about))
140        .route("/dev", get(welcome_controller::dev_info))
141        .merge(auth_routes::router())
142        .merge(auth_protected_routes)"#;
143
144            // Use regex for more robust replacement (includes leading spaces)
145            let re = Regex::new(r#"(?s)Router::new\(\s*\n\s*\.route\("/", get\(welcome_controller::index\)\)\s*\n\s*\.route\("/about", get\(welcome_controller::about\)\)\s*\n\s*\.route\("/dev", get\(welcome_controller::dev_info\)\)"#).unwrap();
146            if re.is_match(&content) {
147                content = re.replace(&content, merge_logic).to_string();
148            } else {
149                // Fallback for simple replacement
150                content = content.replace("Router::new()\n        .route(\"/\", get(welcome_controller::index))\n        .route(\"/about\", get(welcome_controller::about))\n        .route(\"/dev\", get(welcome_controller::dev_info))", merge_logic);
151            }
152            
153            fs::write(web_route_path, content).ok();
154            println!("   {} {}", "📝 Updated:".blue(), web_route_path.cyan());
155        }
156
157    // 3.1 Create Password Resets Migration
158    let timestamp = chrono::Local::now().format("%Y%m%d_%H%M%S").to_string();
159    let migration_name = format!("m{}_create_password_resets_table", timestamp);
160    let migration_path = format!("database/migrations/{}.rs", migration_name);
161    
162    // Check if any password reset migration already exists
163    let mut exists = false;
164    if let Ok(entries) = std::fs::read_dir("database/migrations") {
165        for entry in entries.flatten() {
166            if let Some(name) = entry.file_name().to_str()
167                && name.ends_with("_create_password_resets_table.rs") {
168                    exists = true;
169                    println!("   {} {}", "⚠️  Exists:".yellow(), name.cyan());
170                    break;
171                }
172        }
173    }
174
175    if !exists {
176        let migration_template = r#"use rustbasic_core::sea_orm_migration::prelude::*;
177use rustbasic_core::async_trait;
178
179#[derive(Iden)]
180enum PasswordResets {
181    Table,
182    Email,
183    Token,
184    CreatedAt,
185}
186
187#[derive(DeriveMigrationName)]
188pub struct Migration;
189
190#[async_trait]
191impl MigrationTrait for Migration {
192    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
193        manager
194            .create_table(
195                Table::create()
196                    .table(PasswordResets::Table)
197                    .if_not_exists()
198                    .col(ColumnDef::new(PasswordResets::Email).string().not_null().primary_key())
199                    .col(ColumnDef::new(PasswordResets::Token).string().not_null())
200                    .col(
201                        ColumnDef::new(PasswordResets::CreatedAt)
202                            .timestamp()
203                            .default(Expr::current_timestamp())
204                            .not_null(),
205                    )
206                    .to_owned(),
207            )
208            .await
209    }
210
211    async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
212        manager
213            .drop_table(Table::drop().table(PasswordResets::Table).to_owned())
214            .await
215    }
216}
217"#.to_string();
218        fs::write(&migration_path, migration_template).ok();
219        
220        update_migration_mod_rs(&migration_name);
221        println!("   {} {}", "✅ Created:".green(), format!("Migration {}", migration_name).cyan());
222    }
223
224    // 4. Create Controller Folder & mod.rs
225    let auth_controller_dir = "src/app/http/controllers/auth";
226    fs::create_dir_all(auth_controller_dir).ok();
227    let auth_controller_mod = "src/app/http/controllers/auth/mod.rs";
228    if !std::path::Path::new(auth_controller_mod).exists() {
229        fs::write(auth_controller_mod, "pub mod auth_controller;").ok();
230    }
231    update_controller_mod_rs("auth");
232
233    // 4.1 Create Auth Middleware
234    let auth_middleware_dir = "src/app/http/middleware";
235    fs::create_dir_all(auth_middleware_dir).ok();
236    let auth_middleware_path = "src/app/http/middleware/auth.rs";
237    if !std::path::Path::new(auth_middleware_path).exists() {
238        let middleware_template = r#"use rustbasic_core::axum::{
239    middleware::Next,
240    response::{IntoResponse, Redirect},
241    extract::Request,
242};
243use rustbasic_core::session_manager::RustBasicSessionStore;
244use rustbasic_core::axum_session::Session;
245
246pub async fn auth_middleware(req: Request, next: Next) -> impl IntoResponse {
247    let session = req.extensions().get::<Session<RustBasicSessionStore>>().unwrap();
248    if session.get::<i32>("user_id").is_none() {
249        session.set("error", "Silakan login terlebih dahulu");
250        return Redirect::to("/login").into_response();
251    }
252    next.run(req).await
253}
254
255pub async fn guest_middleware(req: Request, next: Next) -> impl IntoResponse {
256    let session = req.extensions().get::<Session<RustBasicSessionStore>>().unwrap();
257    if session.get::<i32>("user_id").is_some() {
258        return Redirect::to("/dashboard").into_response();
259    }
260    next.run(req).await
261}
262"#;
263        fs::write(auth_middleware_path, middleware_template).ok();
264        
265        // Update src/app/http/middleware/mod.rs
266        let middleware_mod_path = "src/app/http/middleware/mod.rs";
267        if let Ok(mut content) = fs::read_to_string(middleware_mod_path)
268            && !content.contains("pub mod auth;") {
269                content.push_str("pub mod auth;\n");
270                fs::write(middleware_mod_path, content).ok();
271            }
272        println!("   {} {}", "✅ Created:".green(), auth_middleware_path.cyan());
273    }
274
275    // 4.1 Create Password Resets Model
276    let model_path = "src/app/models/password_resets.rs";
277    if !std::path::Path::new(model_path).exists() {
278        let model_template = r#"use rustbasic_core::sea_orm::entity::prelude::*;
279use rustbasic_core::serde::{Deserialize, Serialize};
280
281#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
282#[sea_orm(table_name = "password_resets")]
283pub struct Model {
284    #[sea_orm(primary_key, auto_increment = false)]
285    pub email: String,
286    pub token: String,
287    pub created_at: DateTime,
288}
289
290#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
291pub enum Relation {}
292
293impl ActiveModelBehavior for ActiveModel {}
294"#;
295        fs::write(model_path, model_template).ok();
296        
297        // Update src/app/models/mod.rs
298        let models_mod_path = "src/app/models/mod.rs";
299        if let Ok(mut content) = fs::read_to_string(models_mod_path)
300            && !content.contains("pub mod password_resets;") {
301                content.push_str("pub mod password_resets;\n");
302                fs::write(models_mod_path, content).ok();
303            }
304        println!("   {} {}", "✅ Created:".green(), "Model password_resets".cyan());
305    }
306
307    // 4.2 Create Auth Controller
308    let auth_controller_path = "src/app/http/controllers/auth/auth_controller.rs";
309    if !std::path::Path::new(auth_controller_path).exists() {
310        let controller_template = r#"/* ---------------------------------------------------------
311 * 📑 LABEL: AUTH CONTROLLER (auth/auth_controller.rs)
312 * Menangani pendaftaran, login, dan logout user.
313 * --------------------------------------------------------- */
314
315use crate::app::inertia::inertia;
316use crate::app::models::users;
317use rustbasic_core::requests::Request;
318use rustbasic_core::server::AppState;
319use rustbasic_core::axum::{response::{IntoResponse, Response, Redirect}, extract::State};
320use rustbasic_core::bcrypt::{hash, verify, DEFAULT_COST};
321use rustbasic_core::uuid::Uuid;
322use rustbasic_core::serde::Deserialize;
323use rustbasic_core::validator::Validate;
324use rustbasic_core::mail::MailService;
325use rustbasic_core::sea_orm::{EntityTrait, ColumnTrait, QueryFilter, Set};
326use rustbasic_core::serde_json::json;
327
328#[derive(Deserialize, Validate)]
329pub struct RegisterRequest {
330    #[validate(length(min = 3, message = "Nama minimal 3 karakter"))]
331    pub name: String,
332    
333    #[validate(email(message = "Format email tidak valid"))]
334    pub email: String,
335    
336    #[validate(length(min = 8, message = "Password minimal 8 karakter"))]
337    pub password: String,
338}
339
340#[derive(Deserialize, Validate)]
341pub struct LoginRequest {
342    #[validate(email(message = "Format email tidak valid"))]
343    pub email: String,
344    pub password: String,
345    pub remember: Option<bool>,
346}
347
348#[derive(Deserialize, Validate)]
349pub struct ForgotPasswordRequest {
350    #[validate(email(message = "Format email tidak valid"))]
351    pub email: String,
352}
353
354#[derive(Deserialize, Validate)]
355pub struct ResetPasswordRequest {
356    pub token: String,
357    #[validate(length(min = 8, message = "Password minimal 8 karakter"))]
358    pub password: String,
359}
360
361pub struct AuthController;
362
363impl AuthController {
364    /// Menampilkan halaman login
365    pub async fn login_page(req: Request) -> Response {
366        inertia(&req, "Auth/Login", json!({ "title": "Login" }))
367    }
368
369    /// Menampilkan halaman register
370    pub async fn register_page(req: Request) -> Response {
371        inertia(&req, "Auth/Register", json!({ "title": "Daftar Akun" }))
372    }
373
374    /// Proses Pendaftaran
375    pub async fn register(State(state): State<AppState>, req: Request) -> impl IntoResponse {
376        // 1. Validasi Input
377        let data = match req.validate::<RegisterRequest>() {
378            Ok(d) => d,
379            Err(_) => return Redirect::to("/register").into_response(),
380        };
381
382        // 2. Cek apakah email sudah terdaftar
383        let existing = users::Entity::find()
384            .filter(users::Column::Email.eq(&data.email))
385            .one(&state.db)
386            .await
387            .ok()
388            .flatten();
389
390        if existing.is_some() {
391            req.session.set("error", "Email sudah terdaftar");
392            return Redirect::to("/register").into_response();
393        }
394
395        // 3. Hash Password
396        let hashed = hash(data.password, DEFAULT_COST).unwrap();
397
398        // 4. Simpan ke Database
399        let new_user = users::ActiveModel {
400            name: Set(data.name),
401            email: Set(data.email),
402            password: Set(hashed),
403            ..Default::default()
404        };
405
406        if let Err(e) = users::Entity::insert(new_user).exec(&state.db).await {
407            rustbasic_core::tracing::error!("Gagal menyimpan user: {}", e);
408            req.session.set("error", "Gagal mendaftar, coba lagi.");
409            return Redirect::to("/register").into_response();
410        }
411
412        req.session.set("success", "Pendaftaran berhasil! Silakan login.");
413        Redirect::to("/login").into_response()
414    }
415
416    /// Proses Login
417    pub async fn login(State(state): State<AppState>, req: Request) -> impl IntoResponse {
418        // 1. Validasi Input
419        let data = match req.validate::<LoginRequest>() {
420            Ok(d) => d,
421            Err(_) => return Redirect::to("/login").into_response(),
422        };
423
424        // 2. Ambil User dari DB
425        let user = users::Entity::find()
426            .filter(users::Column::Email.eq(&data.email))
427            .one(&state.db)
428            .await
429            .ok()
430            .flatten();
431
432        if let Some(u) = user {
433            // 3. Verifikasi Password
434            if verify(data.password, &u.password).unwrap_or(false) {
435                // 4. Set Session
436                req.session.set("user_id", u.id);
437                req.session.set("success", "Selamat datang kembali!");
438                return Redirect::to("/dashboard").into_response();
439            }
440        }
441
442        req.session.set("error", "Email atau password salah");
443        Redirect::to("/login").into_response()
444    }
445
446    /// Menampilkan halaman lupa password
447    pub async fn forgot_password_page(req: Request) -> Response {
448        inertia(&req, "Auth/ForgotPassword", json!({ "title": "Lupa Password" }))
449    }
450
451    /// Kirim link reset password
452    pub async fn send_reset_link(State(state): State<AppState>, req: Request) -> impl IntoResponse {
453        let data = match req.validate::<ForgotPasswordRequest>() {
454            Ok(d) => d,
455            Err(_) => return Redirect::to("/forgot-password").into_response(),
456        };
457
458        // 1. Cek apakah user ada
459        let user = users::Entity::find()
460            .filter(users::Column::Email.eq(&data.email))
461            .one(&state.db)
462            .await
463            .ok()
464            .flatten();
465
466        if let Some(u) = user {
467            // 2. Generate Token
468            let token = Uuid::new_v4().to_string();
469
470            // 3. Simpan Token
471            let reset = crate::app::models::password_resets::ActiveModel {
472                email: Set(u.email.clone()),
473                token: Set(token.clone()),
474                created_at: Set(rustbasic_core::chrono::Utc::now().naive_utc()),
475            };
476
477            let _ = crate::app::models::password_resets::Entity::insert(reset)
478                .on_conflict(
479                    rustbasic_core::sea_orm::sea_query::OnConflict::column(crate::app::models::password_resets::Column::Email)
480                        .update_column(crate::app::models::password_resets::Column::Token)
481                        .update_column(crate::app::models::password_resets::Column::CreatedAt)
482                        .to_owned()
483                )
484                .exec(&state.db)
485                .await;
486
487            // 4. Kirim Email (Gunakan Config::load().mail_*)
488            let config = rustbasic_core::Config::load();
489            let app_name = std::env::var("APP_NAME").unwrap_or_else(|_| "RustBasic".to_string());
490            let reset_url = format!("{}/reset-password?token={}", config.app_url, token);
491
492            let subject = format!("Reset Password - {}", app_name);
493            let body = rustbasic_core::view::render_to_string("emails/reset.rb.html", rustbasic_core::minijinja::context! {
494                app_name => app_name,
495                reset_url => reset_url,
496            });
497
498            if let Err(e) = MailService::send_email(&u.email, &subject, &body).await {
499                rustbasic_core::tracing::error!("Gagal mengirim email reset: {}", e);
500            }
501
502            rustbasic_core::tracing::info!("Reset link for {}: {}", u.email, reset_url);
503        }
504
505        req.session.set("success", "Jika email terdaftar, link reset password akan dikirim.");
506        Redirect::to("/login").into_response()
507    }
508
509    /// Menampilkan halaman reset password
510    pub async fn reset_password_page(req: Request) -> Response {
511        let token = req.input_as_str("token").unwrap_or_default();
512        inertia(&req, "Auth/ResetPassword", json!({ "title": "Reset Password", "token": token }))
513    }
514
515    /// Proses update password baru
516    pub async fn update_password(State(state): State<AppState>, req: Request) -> impl IntoResponse {
517        let data = match req.validate::<ResetPasswordRequest>() {
518            Ok(d) => d,
519            Err(_) => return Redirect::to("/login").into_response(),
520        };
521
522        // 1. Cari Token
523        let reset = crate::app::models::password_resets::Entity::find()
524            .filter(crate::app::models::password_resets::Column::Token.eq(&data.token))
525            .one(&state.db)
526            .await
527            .ok()
528            .flatten();
529
530        if let Some(r) = reset {
531            // 2. Cek Kadaluarsa (60 Menit)
532            let now = rustbasic_core::chrono::Utc::now().naive_utc();
533            let duration = now.signed_duration_since(r.created_at);
534            
535            if duration.num_minutes() > 60 {
536                // Hapus token yang sudah kadaluarsa
537                let _ = crate::app::models::password_resets::Entity::delete_by_id(r.email.clone())
538                    .exec(&state.db)
539                    .await;
540                    
541                req.session.set("error", "Tautan reset password sudah kadaluarsa (melebihi 60 menit).");
542                return Redirect::to("/login").into_response();
543            }
544
545            // 3. Hash Password Baru
546            let hashed = rustbasic_core::bcrypt::hash(data.password, rustbasic_core::bcrypt::DEFAULT_COST).unwrap();
547
548            // 4. Update User
549            let _ = users::Entity::update_many()
550                .col_expr(users::Column::Password, rustbasic_core::sea_orm::sea_query::Expr::value(hashed))
551                .filter(users::Column::Email.eq(&r.email))
552                .exec(&state.db)
553                .await;
554
555            // 5. Hapus Token
556            let _ = crate::app::models::password_resets::Entity::delete_by_id(r.email)
557                .exec(&state.db)
558                .await;
559
560            req.session.set("success", "Password berhasil diubah. Silakan login.");
561            return Redirect::to("/login").into_response();
562        }
563
564        req.session.set("error", "Token tidak valid atau sudah kadaluarsa.");
565        Redirect::to("/login").into_response()
566    }
567
568    /// Proses Logout
569    pub async fn logout(req: Request) -> impl IntoResponse {
570        req.session.remove("user_id");
571        req.session.set("success", "Anda telah keluar.");
572        Redirect::to("/").into_response()
573    }
574}
575"#;
576        fs::write(auth_controller_path, controller_template).ok();
577        println!("   {} {}", "✅ Created:".green(), auth_controller_path.cyan());
578    }
579
580    // 5. Views (React Components in resources/js/Pages)
581    let auth_page_dir = "src/resources/js/Pages/Auth";
582    fs::create_dir_all(auth_page_dir).ok();
583
584    // 5.0 Create Components dir & shared components
585    let components_dir = "src/resources/js/Components";
586    fs::create_dir_all(components_dir).ok();
587
588    let toast_template = r##"import React, { useState, useEffect, useCallback } from 'react';
589
590const ICONS = {
591  success: (
592    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
593      <circle cx="10" cy="10" r="10" fill="currentColor" opacity="0.15" />
594      <path d="M6 10.5L8.5 13L14 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
595    </svg>
596  ),
597  error: (
598    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
599      <circle cx="10" cy="10" r="10" fill="currentColor" opacity="0.15" />
600      <path d="M7 7L13 13M13 7L7 13" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
601    </svg>
602  ),
603  warning: (
604    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
605      <circle cx="10" cy="10" r="10" fill="currentColor" opacity="0.15" />
606      <path d="M10 6V11" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
607      <circle cx="10" cy="14" r="1" fill="currentColor" />
608    </svg>
609  ),
610  info: (
611    <svg width="20" height="20" viewBox="0 0 20 20" fill="none">
612      <circle cx="10" cy="10" r="10" fill="currentColor" opacity="0.15" />
613      <path d="M10 9V14" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
614      <circle cx="10" cy="6.5" r="1" fill="currentColor" />
615    </svg>
616  ),
617};
618
619const STYLES = {
620  success: {
621    bg: 'rgba(16, 185, 129, 0.08)',
622    border: 'rgba(16, 185, 129, 0.25)',
623    color: '#34d399',
624    progress: '#10b981',
625    shadow: '0 8px 32px rgba(16, 185, 129, 0.15)',
626  },
627  error: {
628    bg: 'rgba(244, 63, 94, 0.08)',
629    border: 'rgba(244, 63, 94, 0.25)',
630    color: '#fb7185',
631    progress: '#f43f5e',
632    shadow: '0 8px 32px rgba(244, 63, 94, 0.15)',
633  },
634  warning: {
635    bg: 'rgba(245, 158, 11, 0.08)',
636    border: 'rgba(245, 158, 11, 0.25)',
637    color: '#fbbf24',
638    progress: '#f59e0b',
639    shadow: '0 8px 32px rgba(245, 158, 11, 0.15)',
640  },
641  info: {
642    bg: 'rgba(99, 102, 241, 0.08)',
643    border: 'rgba(99, 102, 241, 0.25)',
644    color: '#818cf8',
645    progress: '#6366f1',
646    shadow: '0 8px 32px rgba(99, 102, 241, 0.15)',
647  },
648};
649
650function SingleToast({ id, type, message, duration = 5000, onDismiss }) {
651  const [isVisible, setIsVisible] = useState(false);
652  const [isLeaving, setIsLeaving] = useState(false);
653  const [progress, setProgress] = useState(100);
654
655  const style = STYLES[type] || STYLES.info;
656
657  const dismiss = useCallback(() => {
658    setIsLeaving(true);
659    setTimeout(() => onDismiss(id), 350);
660  }, [id, onDismiss]);
661
662  useEffect(() => {
663    const enterTimer = setTimeout(() => setIsVisible(true), 10);
664    const dismissTimer = setTimeout(() => dismiss(), duration);
665    const startTime = Date.now();
666    const progressInterval = setInterval(() => {
667      const elapsed = Date.now() - startTime;
668      const remaining = Math.max(0, 100 - (elapsed / duration) * 100);
669      setProgress(remaining);
670      if (remaining <= 0) clearInterval(progressInterval);
671    }, 30);
672
673    return () => {
674      clearTimeout(enterTimer);
675      clearTimeout(dismissTimer);
676      clearInterval(progressInterval);
677    };
678  }, [duration, dismiss]);
679
680  return (
681    <div
682      style={{
683        background: style.bg,
684        border: `1px solid ${style.border}`,
685        borderRadius: '16px',
686        padding: '14px 18px',
687        marginBottom: '10px',
688        display: 'flex',
689        alignItems: 'flex-start',
690        gap: '12px',
691        color: style.color,
692        backdropFilter: 'blur(20px)',
693        boxShadow: style.shadow,
694        transform: isVisible && !isLeaving
695          ? 'translateX(0) scale(1)'
696          : isLeaving
697            ? 'translateX(120%) scale(0.9)'
698            : 'translateX(120%) scale(0.9)',
699        opacity: isVisible && !isLeaving ? 1 : 0,
700        transition: 'all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)',
701        position: 'relative',
702        overflow: 'hidden',
703        minWidth: '320px',
704        maxWidth: '420px',
705        cursor: 'pointer',
706      }}
707      onClick={dismiss}
708      role="alert"
709    >
710      <div style={{ flexShrink: 0, marginTop: '1px' }}>{ICONS[type] || ICONS.info}</div>
711      <div style={{ flex: 1, minWidth: 0 }}>
712        <div style={{
713          fontSize: '11px',
714          fontWeight: 700,
715          textTransform: 'uppercase',
716          letterSpacing: '0.08em',
717          opacity: 0.7,
718          marginBottom: '2px',
719        }}>
720          {type === 'success' && 'Berhasil'}
721          {type === 'error' && 'Kesalahan'}
722          {type === 'warning' && 'Peringatan'}
723          {type === 'info' && 'Informasi'}
724        </div>
725        <div style={{
726          fontSize: '13px',
727          fontWeight: 500,
728          color: '#e2e8f0',
729          lineHeight: 1.5,
730          wordBreak: 'break-word',
731        }}>{message}</div>
732      </div>
733      <button
734        onClick={(e) => { e.stopPropagation(); dismiss(); }}
735        style={{
736          background: 'none',
737          border: 'none',
738          color: style.color,
739          cursor: 'pointer',
740          padding: '2px',
741          opacity: 0.5,
742          transition: 'opacity 0.2s',
743          flexShrink: 0,
744          marginTop: '1px',
745        }}
746      >
747748      </button>
749      <div style={{
750        position: 'absolute',
751        bottom: 0,
752        left: 0,
753        right: 0,
754        height: '3px',
755        background: `${style.progress}15`,
756        borderRadius: '0 0 16px 16px',
757        overflow: 'hidden',
758      }}>
759        <div style={{
760          width: `${progress}%`,
761          height: '100%',
762          background: `linear-gradient(90deg, ${style.progress}, ${style.progress}aa)`,
763          transition: 'width 0.1s linear',
764          borderRadius: '0 0 16px 16px',
765        }} />
766      </div>
767    </div>
768  );
769}
770
771export default function Toast({ flash, duration = 5000, position = 'top-right' }) {
772  const [toasts, setToasts] = useState([]);
773
774  useEffect(() => {
775    if (!flash) return;
776    const newToasts = [];
777    if (flash.success) newToasts.push({ id: Date.now() + '_s', type: 'success', message: flash.success });
778    if (flash.error) newToasts.push({ id: Date.now() + '_e', type: 'error', message: flash.error });
779    if (flash.warning) newToasts.push({ id: Date.now() + '_w', type: 'warning', message: flash.warning });
780    if (flash.info) newToasts.push({ id: Date.now() + '_i', type: 'info', message: flash.info });
781
782    if (newToasts.length > 0) {
783      setToasts(prev => [...prev, ...newToasts]);
784    }
785  }, [flash?.success, flash?.error, flash?.warning, flash?.info]);
786
787  const handleDismiss = useCallback((id) => {
788    setToasts(prev => prev.filter(t => t.id !== id));
789  }, []);
790
791  const positionStyle = {
792    'top-right': { top: '24px', right: '24px' },
793    'top-left': { top: '24px', left: '24px' },
794    'bottom-right': { bottom: '24px', right: '24px' },
795    'bottom-left': { bottom: '24px', left: '24px' },
796  };
797
798  if (toasts.length === 0) return null;
799
800  return (
801    <div style={{ position: 'fixed', zIndex: 99999, pointerEvents: 'none', ...positionStyle[position] }}>
802      <div style={{ pointerEvents: 'auto' }}>
803        {toasts.map(toast => (
804          <SingleToast
805            key={toast.id}
806            id={toast.id}
807            type={toast.type}
808            message={toast.message}
809            duration={duration}
810            onDismiss={handleDismiss}
811          />
812        ))}
813      </div>
814    </div>
815  );
816}
817"##;
818
819    let alert_banner_template = r##"import React from 'react';
820
821const ALERT_STYLES = {
822  success: {
823    bg: 'rgba(16, 185, 129, 0.08)',
824    border: 'rgba(16, 185, 129, 0.2)',
825    color: '#34d399',
826    icon: '✅',
827  },
828  error: {
829    bg: 'rgba(244, 63, 94, 0.08)',
830    border: 'rgba(244, 63, 94, 0.2)',
831    color: '#fb7185',
832    icon: '❌',
833  },
834  warning: {
835    bg: 'rgba(245, 158, 11, 0.08)',
836    border: 'rgba(245, 158, 11, 0.2)',
837    color: '#fbbf24',
838    icon: '⚠️',
839  },
840  info: {
841    bg: 'rgba(99, 102, 241, 0.08)',
842    border: 'rgba(99, 102, 241, 0.2)',
843    color: '#818cf8',
844    icon: 'ℹ️',
845  },
846};
847
848export default function AlertBanner({ type = 'info', message, onDismiss }) {
849  if (!message) return null;
850  const style = ALERT_STYLES[type] || ALERT_STYLES.info;
851
852  return (
853    <div
854      role="alert"
855      style={{
856        background: style.bg,
857        border: `1px solid ${style.border}`,
858        borderRadius: '14px',
859        padding: '14px 18px',
860        marginBottom: '20px',
861        display: 'flex',
862        alignItems: 'center',
863        gap: '10px',
864        animation: 'alertSlideIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1)',
865      }}
866    >
867      <span style={{ fontSize: '16px', flexShrink: 0 }}>{style.icon}</span>
868      <span style={{ flex: 1, fontSize: '13px', fontWeight: 600, color: style.color, lineHeight: 1.5 }}>{message}</span>
869      {onDismiss && (
870        <button
871          onClick={onDismiss}
872          style={{
873            background: 'none',
874            border: 'none',
875            color: style.color,
876            cursor: 'pointer',
877            padding: '2px 4px',
878            opacity: 0.6,
879            transition: 'opacity 0.2s',
880            fontSize: '14px',
881          }}
882        >
883884        </button>
885      )}
886      <style>{`
887        @keyframes alertSlideIn {
888          from { opacity: 0; transform: translateY(-8px) scale(0.97); }
889          to { opacity: 1; transform: translateY(0) scale(1); }
890        }
891      `}</style>
892    </div>
893  );
894}
895"##;
896
897    let form_input_template = r##"import React from 'react';
898
899export default function FormInput({
900  label,
901  type = 'text',
902  value,
903  onChange,
904  error,
905  placeholder,
906  required = false,
907  autoFocus = false,
908  disabled = false,
909}) {
910  const hasError = !!error;
911
912  return (
913    <div>
914      {label && (
915        <label style={{
916          display: 'block',
917          fontSize: '11px',
918          fontWeight: 700,
919          textTransform: 'uppercase',
920          letterSpacing: '0.08em',
921          marginBottom: '8px',
922          color: hasError ? '#fb7185' : '#94a3b8',
923          transition: 'color 0.3s ease',
924        }}>{label}</label>
925      )}
926      <input
927        type={type}
928        value={value}
929        onChange={onChange}
930        placeholder={placeholder}
931        required={required}
932        autoFocus={autoFocus}
933        disabled={disabled}
934        style={{
935          width: '100%',
936          boxSizing: 'border-box',
937          background: 'rgba(2, 6, 23, 0.8)',
938          border: `1px solid ${hasError ? 'rgba(244, 63, 94, 0.5)' : 'rgba(30, 41, 59, 1)'}`,
939          borderRadius: '12px',
940          padding: '12px 14px',
941          fontSize: '14px',
942          color: '#ffffff',
943          outline: 'none',
944          transition: 'all 0.3s ease',
945          opacity: disabled ? 0.5 : 1,
946        }}
947        onFocus={(e) => {
948          e.target.style.borderColor = hasError ? 'rgba(244, 63, 94, 0.7)' : 'rgba(99, 102, 241, 0.5)';
949          e.target.style.boxShadow = hasError ? '0 0 0 3px rgba(244, 63, 94, 0.1)' : '0 0 0 3px rgba(99, 102, 241, 0.1)';
950        }}
951        onBlur={(e) => {
952          e.target.style.borderColor = hasError ? 'rgba(244, 63, 94, 0.5)' : 'rgba(30, 41, 59, 1)';
953          e.target.style.boxShadow = 'none';
954        }}
955      />
956      {hasError && (
957        <div style={{
958          display: 'flex',
959          alignItems: 'center',
960          gap: '5px',
961          marginTop: '6px',
962          animation: 'errorShake 0.4s cubic-bezier(0.36, 0.07, 0.19, 0.97)',
963        }}>
964          <svg width="12" height="12" viewBox="0 0 12 12" fill="none" style={{ flexShrink: 0 }}>
965            <circle cx="6" cy="6" r="6" fill="rgba(244, 63, 94, 0.15)" />
966            <path d="M6 3.5V6.5" stroke="#fb7185" strokeWidth="1.2" strokeLinecap="round" />
967            <circle cx="6" cy="8.2" r="0.6" fill="#fb7185" />
968          </svg>
969          <span style={{ fontSize: '12px', fontWeight: 600, color: '#fb7185', lineHeight: 1.3 }}>{error}</span>
970        </div>
971      )}
972      <style>{`
973        @keyframes errorShake {
974          0%, 100% { transform: translateX(0); }
975          20% { transform: translateX(-4px); }
976          40% { transform: translateX(4px); }
977          60% { transform: translateX(-2px); }
978          80% { transform: translateX(2px); }
979        }
980      `}</style>
981    </div>
982  );
983}
984"##;
985
986    let login_template = r##"import React from 'react';
987import { Link, useForm, usePage } from '@inertiajs/react';
988import Toast from '../../Components/Toast';
989import AlertBanner from '../../Components/AlertBanner';
990import FormInput from '../../Components/FormInput';
991
992export default function Login() {
993  const { flash } = usePage().props;
994  const { data, setData, post, processing, errors } = useForm({
995    email: '',
996    password: '',
997    remember: false,
998  });
999
1000  const handleSubmit = (e) => {
1001    e.preventDefault();
1002    post('/login');
1003  };
1004
1005  return (
1006    <div className="min-h-screen bg-gradient-to-tr from-slate-950 via-slate-900 to-indigo-950 flex items-center justify-center p-6 text-slate-100 font-sans">
1007      <Toast flash={flash} />
1008
1009      <div className="w-full max-w-md bg-slate-900/60 backdrop-blur-md border border-slate-800/80 rounded-3xl p-8 shadow-2xl relative overflow-hidden glassmorphism">
1010        <div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none" />
1011        <div className="absolute bottom-0 left-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl pointer-events-none" />
1012        
1013        <div className="text-center mb-8">
1014          <span className="text-xs font-bold tracking-widest text-indigo-400 bg-indigo-500/10 px-3 py-1 rounded-full border border-indigo-500/20 uppercase">
1015            RustBasic SPA
1016          </span>
1017          <h1 className="text-3xl font-extrabold text-white mt-4 tracking-tight">Selamat Datang</h1>
1018          <p className="text-slate-400 text-sm mt-2">Silakan masuk ke akun Anda</p>
1019        </div>
1020
1021        {flash?.success && <AlertBanner type="success" message={flash.success} />}
1022        {flash?.error && <AlertBanner type="error" message={flash.error} />}
1023
1024        <form onSubmit={handleSubmit} className="space-y-5">
1025          <FormInput
1026            label="Email Address"
1027            type="email"
1028            value={data.email}
1029            onChange={(e) => setData('email', e.target.value)}
1030            error={errors.email}
1031            placeholder="nama@email.com"
1032            required
1033          />
1034
1035          <FormInput
1036            label="Password"
1037            type="password"
1038            value={data.password}
1039            onChange={(e) => setData('password', e.target.value)}
1040            error={errors.password}
1041            placeholder="••••••••"
1042            required
1043          />
1044
1045          <div className="flex items-center justify-between text-sm">
1046            <label className="flex items-center space-x-2 text-slate-400 cursor-pointer">
1047              <input
1048                type="checkbox"
1049                checked={data.remember}
1050                onChange={(e) => setData('remember', e.target.checked)}
1051                className="w-4 h-4 rounded border-slate-800 bg-slate-950 text-indigo-600 focus:ring-indigo-500 focus:ring-opacity-25"
1052              />
1053              <span className="select-none">Ingat Saya</span>
1054            </label>
1055            <Link href="/forgot-password" className="text-indigo-400 hover:text-indigo-300 font-semibold transition-colors duration-200" style={{ textDecoration: 'none' }}>
1056              Lupa Password?
1057            </Link>
1058          </div>
1059
1060          <button
1061            type="submit"
1062            disabled={processing}
1063            className="w-full py-3.5 px-4 bg-gradient-to-r from-indigo-600 to-indigo-700 hover:from-indigo-500 hover:to-indigo-600 text-white rounded-xl font-bold tracking-wide shadow-[0_0_20px_rgba(99,102,241,0.25)] hover:shadow-[0_0_25px_rgba(99,102,241,0.4)] disabled:opacity-50 transition-all duration-300 transform active:scale-[0.98]"
1064          >
1065            {processing ? 'MEMROSES...' : 'MASUK KE DASHBOARD'}
1066          </button>
1067        </form>
1068
1069        <p className="text-center text-sm text-slate-500 mt-8">
1070          Belum punya akun?{' '}
1071          <Link href="/register" className="text-indigo-400 hover:underline font-bold transition-colors duration-200" style={{ textDecoration: 'none' }}>
1072            Daftar Sekarang
1073          </Link>
1074        </p>
1075      </div>
1076    </div>
1077  );
1078}
1079"##;
1080
1081    let register_template = r##"import React from 'react';
1082import { Link, useForm, usePage } from '@inertiajs/react';
1083import Toast from '../../Components/Toast';
1084import AlertBanner from '../../Components/AlertBanner';
1085import FormInput from '../../Components/FormInput';
1086
1087export default function Register() {
1088  const { flash } = usePage().props;
1089  const { data, setData, post, processing, errors } = useForm({
1090    name: '',
1091    email: '',
1092    password: '',
1093  });
1094
1095  const handleSubmit = (e) => {
1096    e.preventDefault();
1097    post('/register');
1098  };
1099
1100  return (
1101    <div className="min-h-screen bg-gradient-to-tr from-slate-950 via-slate-900 to-indigo-950 flex items-center justify-center p-6 text-slate-100 font-sans">
1102      <Toast flash={flash} />
1103
1104      <div className="w-full max-w-md bg-slate-900/60 backdrop-blur-md border border-slate-800/80 rounded-3xl p-8 shadow-2xl relative overflow-hidden glassmorphism">
1105        <div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none" />
1106        <div className="absolute bottom-0 left-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl pointer-events-none" />
1107        
1108        <div className="text-center mb-8">
1109          <span className="text-xs font-bold tracking-widest text-indigo-400 bg-indigo-500/10 px-3 py-1 rounded-full border border-indigo-500/20 uppercase">
1110            RustBasic SPA
1111          </span>
1112          <h1 className="text-3xl font-extrabold text-white mt-4 tracking-tight">Daftar Akun</h1>
1113          <p className="text-slate-400 text-sm mt-2">Mulai perjalanan Anda bersama kami</p>
1114        </div>
1115
1116        {flash?.error && <AlertBanner type="error" message={flash.error} />}
1117        {flash?.success && <AlertBanner type="success" message={flash.success} />}
1118
1119        <form onSubmit={handleSubmit} className="space-y-5">
1120          <FormInput
1121            label="Nama Lengkap"
1122            type="text"
1123            value={data.name}
1124            onChange={(e) => setData('name', e.target.value)}
1125            error={errors.name}
1126            placeholder="Nama Lengkap Anda"
1127            required
1128          />
1129
1130          <FormInput
1131            label="Email Address"
1132            type="email"
1133            value={data.email}
1134            onChange={(e) => setData('email', e.target.value)}
1135            error={errors.email}
1136            placeholder="nama@email.com"
1137            required
1138          />
1139
1140          <FormInput
1141            label="Password"
1142            type="password"
1143            value={data.password}
1144            onChange={(e) => setData('password', e.target.value)}
1145            error={errors.password}
1146            placeholder="Min. 8 karakter"
1147            required
1148          />
1149
1150          <button
1151            type="submit"
1152            disabled={processing}
1153            className="w-full py-3.5 px-4 bg-gradient-to-r from-indigo-600 to-indigo-700 hover:from-indigo-500 hover:to-indigo-600 text-white rounded-xl font-bold tracking-wide shadow-[0_0_20px_rgba(99,102,241,0.25)] hover:shadow-[0_0_25px_rgba(99,102,241,0.4)] disabled:opacity-50 transition-all duration-300 transform active:scale-[0.98]"
1154          >
1155            {processing ? 'MENDAFTAR...' : 'BUAT AKUN SEKARANG'}
1156          </button>
1157        </form>
1158
1159        <p className="text-center text-sm text-slate-500 mt-8">
1160          Sudah punya akun?{' '}
1161          <Link href="/login" className="text-indigo-400 hover:underline font-bold transition-colors duration-200" style={{ textDecoration: 'none' }}>
1162            Login Disini
1163          </Link>
1164        </p>
1165      </div>
1166    </div>
1167  );
1168}
1169"##;
1170
1171    let forgot_template = r##"import React from 'react';
1172import { Link, useForm, usePage } from '@inertiajs/react';
1173import Toast from '../../Components/Toast';
1174import AlertBanner from '../../Components/AlertBanner';
1175import FormInput from '../../Components/FormInput';
1176
1177export default function ForgotPassword() {
1178  const { flash } = usePage().props;
1179  const { data, setData, post, processing, errors } = useForm({
1180    email: '',
1181  });
1182
1183  const handleSubmit = (e) => {
1184    e.preventDefault();
1185    post('/forgot-password');
1186  };
1187
1188  return (
1189    <div className="min-h-screen bg-gradient-to-tr from-slate-950 via-slate-900 to-indigo-950 flex items-center justify-center p-6 text-slate-100 font-sans">
1190      <Toast flash={flash} />
1191
1192      <div className="w-full max-w-md bg-slate-900/60 backdrop-blur-md border border-slate-800/80 rounded-3xl p-8 shadow-2xl relative overflow-hidden glassmorphism">
1193        <div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none" />
1194        <div className="absolute bottom-0 left-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl pointer-events-none" />
1195        
1196        <div className="text-center mb-8">
1197          <span className="text-xs font-bold tracking-widest text-indigo-400 bg-indigo-500/10 px-3 py-1 rounded-full border border-indigo-500/20 uppercase">
1198            Keamanan Akun
1199          </span>
1200          <h1 className="text-3xl font-extrabold text-white mt-4 tracking-tight">Lupa Password</h1>
1201          <p className="text-slate-400 text-sm mt-2">Kami akan mengirimkan instruksi ke email Anda</p>
1202        </div>
1203
1204        {flash?.success && <AlertBanner type="success" message={flash.success} />}
1205        {flash?.error && <AlertBanner type="error" message={flash.error} />}
1206
1207        <form onSubmit={handleSubmit} className="space-y-5">
1208          <FormInput
1209            label="Email Address"
1210            type="email"
1211            value={data.email}
1212            onChange={(e) => setData('email', e.target.value)}
1213            error={errors.email}
1214            placeholder="nama@email.com"
1215            required
1216            autoFocus
1217          />
1218
1219          <button
1220            type="submit"
1221            disabled={processing}
1222            className="w-full py-3.5 px-4 bg-gradient-to-r from-indigo-600 to-indigo-700 hover:from-indigo-500 hover:to-indigo-600 text-white rounded-xl font-bold tracking-wide shadow-[0_0_20px_rgba(99,102,241,0.25)] hover:shadow-[0_0_25px_rgba(99,102,241,0.4)] disabled:opacity-50 transition-all duration-300 transform active:scale-[0.98]"
1223          >
1224            {processing ? 'MENGIRIM...' : 'KIRIM LINK RESET PASSWORD'}
1225          </button>
1226        </form>
1227
1228        <p className="text-center text-sm text-slate-500 mt-8">
1229          Ingat password Anda?{' '}
1230          <Link href="/login" className="text-indigo-400 hover:underline font-bold transition-colors duration-200" style={{ textDecoration: 'none' }}>
1231            Login Disini
1232          </Link>
1233        </p>
1234      </div>
1235    </div>
1236  );
1237}
1238"##;
1239
1240    let toast_view = "src/resources/js/Components/Toast.jsx";
1241    if !std::path::Path::new(toast_view).exists() {
1242        fs::write(toast_view, toast_template).ok();
1243    }
1244
1245    let alert_banner_view = "src/resources/js/Components/AlertBanner.jsx";
1246    if !std::path::Path::new(alert_banner_view).exists() {
1247        fs::write(alert_banner_view, alert_banner_template).ok();
1248    }
1249
1250    let form_input_view = "src/resources/js/Components/FormInput.jsx";
1251    if !std::path::Path::new(form_input_view).exists() {
1252        fs::write(form_input_view, form_input_template).ok();
1253    }
1254
1255    let login_view = "src/resources/js/Pages/Auth/Login.jsx";
1256    if !std::path::Path::new(login_view).exists() {
1257        fs::write(login_view, login_template).ok();
1258    }
1259    
1260    let register_view = "src/resources/js/Pages/Auth/Register.jsx";
1261    if !std::path::Path::new(register_view).exists() {
1262        fs::write(register_view, register_template).ok();
1263    }
1264
1265    let forgot_view = "src/resources/js/Pages/Auth/ForgotPassword.jsx";
1266    if !std::path::Path::new(forgot_view).exists() {
1267        fs::write(forgot_view, forgot_template).ok();
1268    }
1269
1270    let reset_view = "src/resources/js/Pages/Auth/ResetPassword.jsx";
1271    if !std::path::Path::new(reset_view).exists() {
1272        let reset_template = r##"import React from 'react';
1273import { useForm, usePage } from '@inertiajs/react';
1274import Toast from '../../Components/Toast';
1275import AlertBanner from '../../Components/AlertBanner';
1276import FormInput from '../../Components/FormInput';
1277
1278export default function ResetPassword({ token }) {
1279  const { flash } = usePage().props;
1280  const { data, setData, post, processing, errors } = useForm({
1281    token: token || '',
1282    password: '',
1283  });
1284
1285  const handleSubmit = (e) => {
1286    e.preventDefault();
1287    post('/reset-password');
1288  };
1289
1290  return (
1291    <div className="min-h-screen bg-gradient-to-tr from-slate-950 via-slate-900 to-indigo-950 flex items-center justify-center p-6 text-slate-100 font-sans">
1292      <Toast flash={flash} />
1293
1294      <div className="w-full max-w-md bg-slate-900/60 backdrop-blur-md border border-slate-800/80 rounded-3xl p-8 shadow-2xl relative overflow-hidden glassmorphism">
1295        <div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none" />
1296        <div className="absolute bottom-0 left-0 w-32 h-32 bg-purple-500/10 rounded-full blur-3xl pointer-events-none" />
1297        
1298        <div className="text-center mb-8">
1299          <span className="text-xs font-bold tracking-widest text-indigo-400 bg-indigo-500/10 px-3 py-1 rounded-full border border-indigo-500/20 uppercase">
1300            Akses Akun
1301          </span>
1302          <h1 className="text-3xl font-extrabold text-white mt-4 tracking-tight">Reset Password</h1>
1303          <p className="text-slate-400 text-sm mt-2">Silakan masukkan password baru Anda</p>
1304        </div>
1305
1306        {flash?.error && <AlertBanner type="error" message={flash.error} />}
1307
1308        <form onSubmit={handleSubmit} className="space-y-5">
1309          <input type="hidden" value={data.token} />
1310
1311          <FormInput
1312            label="Password Baru"
1313            type="password"
1314            value={data.password}
1315            onChange={(e) => setData('password', e.target.value)}
1316            error={errors.password}
1317            placeholder="Minimal 8 karakter"
1318            required
1319            autoFocus
1320          />
1321
1322          <button
1323            type="submit"
1324            disabled={processing}
1325            className="w-full py-3.5 px-4 bg-gradient-to-r from-indigo-600 to-indigo-700 hover:from-indigo-500 hover:to-indigo-600 text-white rounded-xl font-bold tracking-wide shadow-[0_0_20px_rgba(99,102,241,0.25)] hover:shadow-[0_0_25px_rgba(99,102,241,0.4)] disabled:opacity-50 transition-all duration-300 transform active:scale-[0.98]"
1326          >
1327            {processing ? 'MENYIMPAN...' : 'SIMPAN PASSWORD BARU'}
1328          </button>
1329        </form>
1330      </div>
1331    </div>
1332  );
1333}
1334"##;
1335        fs::write(reset_view, reset_template).ok();
1336    }
1337
1338    // 5.1 Create Email Reset Template (Minijinja string processing is still perfect here)
1339    let email_reset_view = "src/resources/views/emails/reset.rb.html";
1340    if !std::path::Path::new(email_reset_view).exists() {
1341        fs::create_dir_all("src/resources/views/emails").ok();
1342        let email_reset_template = r##"<!DOCTYPE html>
1343<html>
1344<head>
1345    <meta charset="utf-8">
1346    <style>
1347        body { font-family: 'Inter', -apple-system, sans-serif; line-height: 1.6; color: #1a1a1a; margin: 0; padding: 0; }
1348        .container { max-width: 600px; margin: 0 auto; padding: 40px 20px; }
1349        .card { background: #ffffff; border-radius: 16px; overflow: hidden; box-shadow: 0 4px 24px rgba(0,0,0,0.06); border: 1px solid #f0f0f0; }
1350        .header { background: linear-gradient(135deg, #6366f1, #a855f7); padding: 40px; text-align: center; color: white; }
1351        .content { padding: 40px; }
1352        .button { display: inline-block; padding: 14px 32px; background: #6366f1; color: #ffffff !important; text-decoration: none; border-radius: 8px; font-weight: 600; margin: 24px 0; }
1353        .footer { padding: 24px; text-align: center; font-size: 13px; color: #6b7280; }
1354        h1 { margin: 0; font-size: 24px; font-weight: 800; letter-spacing: -0.025em; }
1355        p { margin: 16px 0; color: #4b5563; }
1356        .divider { height: 1px; background: #f3f4f6; margin: 24px 0; }
1357    </style>
1358</head>
1359<body>
1360    <div class="container">
1361        <div class="card">
1362            <div class="header">
1363                <h1>{{ app_name }}</h1>
1364            </div>
1365            <div class="content">
1366                <h2 style="margin: 0; color: #111827; font-size: 20px;">Halo!</h2>
1367                <p>Anda menerima email ini karena kami menerima permintaan reset password untuk akun Anda di <strong>{{ app_name }}</strong>.</p>
1368                
1369                <div style="text-align: center;">
1370                    <a href="{{ reset_url }}" class="button">Reset Password Saya</a>
1371                </div>
1372
1373                <p style="font-size: 14px; color: #9ca3af;">Link ini akan kadaluarsa dalam 60 menit. Jika Anda tidak merasa meminta reset password, abaikan saja email ini.</p>
1374                
1375                <div class="divider"></div>
1376                
1377                <p style="font-size: 12px; color: #9ca3af;">
1378                    Jika Anda kesulitan menekan tombol, salin dan tempel URL berikut ke browser Anda:<br>
1379                    <span style="word-break: break-all; color: #6366f1;">{{ reset_url }}</span>
1380                </p>
1381            </div>
1382        </div>
1383        <div class="footer">
1384            &copy; 2026 {{ app_name }}. All rights reserved.
1385        </div>
1386    </div>
1387</body>
1388</html>
1389"##;
1390        fs::write(email_reset_view, email_reset_template).ok();
1391    }
1392
1393    // 5.2 Create Dashboard Page in React
1394    let dashboard_view = "src/resources/js/Pages/Dashboard.jsx";
1395    if !std::path::Path::new(dashboard_view).exists() {
1396        let dashboard_template = r##"import React from 'react';
1397import { Link, router, usePage } from '@inertiajs/react';
1398import Toast from '../Components/Toast';
1399
1400export default function Dashboard({ title, userName, userEmail, totalUsers }) {
1401  const { flash } = usePage().props;
1402
1403  const handleLogout = (e) => {
1404    e.preventDefault();
1405    router.post('/logout');
1406  };
1407
1408  return (
1409    <div className="min-h-screen bg-slate-950 text-slate-100 flex flex-col md:flex-row font-sans">
1410      {/* Sidebar */}
1411      <aside className="w-full md:w-80 bg-slate-900 border-b md:border-b-0 md:border-r border-slate-800/80 p-6 flex flex-col justify-between relative overflow-hidden">
1412        <div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/5 rounded-full blur-3xl pointer-events-none" />
1413        
1414        <div>
1415          {/* Logo */}
1416          <div className="flex items-center space-x-3 mb-10">
1417            <div className="w-10 h-10 bg-indigo-600 rounded-xl flex items-center justify-center font-extrabold text-white text-lg shadow-lg shadow-indigo-600/30">
1418              R
1419            </div>
1420            <span className="text-xl font-extrabold text-white tracking-tight">RustBasic</span>
1421          </div>
1422
1423          {/* User Profile Info Card */}
1424          <div className="bg-slate-950/60 border border-slate-800/50 rounded-2xl p-4 mb-8">
1425            <div className="flex items-center space-x-3">
1426              <div className="w-12 h-12 bg-gradient-to-tr from-indigo-500 to-purple-500 rounded-full flex items-center justify-center font-extrabold text-white text-lg">
1427                {userName ? userName[0].toUpperCase() : 'G'}
1428              </div>
1429              <div className="overflow-hidden">
1430                <h4 className="text-sm font-bold text-white truncate">{userName || 'Administrator'}</h4>
1431                <p className="text-xs text-slate-500 truncate">{userEmail || 'admin@rustbasic.dev'}</p>
1432              </div>
1433            </div>
1434          </div>
1435
1436          {/* Navigation links */}
1437          <nav className="space-y-2">
1438            <Link
1439              href="/dashboard"
1440              className="flex items-center space-x-3 w-full px-4 py-3 bg-indigo-600 text-white rounded-xl font-bold text-sm shadow-lg shadow-indigo-600/10 transition-all duration-300"
1441              style={{ textDecoration: 'none' }}
1442            >
1443              <span>📊</span>
1444              <span>Dashboard Overview</span>
1445            </Link>
1446            <Link
1447              href="/"
1448              className="flex items-center space-x-3 w-full px-4 py-3 text-slate-400 hover:text-white rounded-xl font-semibold text-sm hover:bg-slate-800/30 transition-all duration-300"
1449              style={{ textDecoration: 'none' }}
1450            >
1451              <span>🏠</span>
1452              <span>Main Website</span>
1453            </Link>
1454          </nav>
1455        </div>
1456
1457        {/* Logout Form / Button */}
1458        <div className="mt-8 md:mt-0">
1459          <form onSubmit={handleLogout}>
1460            <button
1461              type="submit"
1462              className="w-full py-3 px-4 bg-rose-500/10 hover:bg-rose-500/20 border border-rose-500/20 text-rose-400 rounded-xl font-bold text-sm transition-all duration-300 flex items-center justify-center space-x-2"
1463            >
1464              <span>🚪</span>
1465              <span>KELUAR SISTEM</span>
1466            </button>
1467          </form>
1468        </div>
1469      </aside>
1470
1471      {/* Main Workspace */}
1472      <main className="flex-1 p-6 md:p-12 overflow-y-auto">
1473        <div className="max-w-6xl mx-auto">
1474          {/* Header */}
1475          <header className="flex flex-col md:flex-row md:items-center md:justify-between mb-10 gap-4">
1476            <div>
1477              <h1 className="text-3xl font-extrabold text-white tracking-tight">{title || 'Overview'}</h1>
1478              <p className="text-slate-400 text-sm mt-1">Selamat datang kembali, kendalikan project Anda secara instan.</p>
1479            </div>
1480            <div>
1481              <span className="inline-flex items-center px-4 py-2 bg-slate-900 border border-slate-800 rounded-xl text-xs font-bold text-slate-300 shadow-sm">
1482                <span className="w-2.5 h-2.5 bg-emerald-500 rounded-full mr-2 animate-ping" />
1483                Server Status: <span className="text-emerald-400 ml-1">Running</span>
1484              </span>
1485            </div>
1486          </header>
1487
1488          {/* Toast Notifications */}
1489          <Toast flash={flash} />
1490
1491          {/* Stats Grid */}
1492          <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mb-10">
1493            {/* Stat 1 */}
1494            <div className="bg-slate-900/60 border border-slate-800/80 rounded-3xl p-6 relative overflow-hidden glassmorphism">
1495              <span className="text-xs font-bold text-slate-500 uppercase tracking-widest block mb-4">
1496                User Terdaftar
1497              </span>
1498              <div className="flex items-baseline space-x-2">
1499                <span className="text-5xl font-black text-white tracking-tight">{totalUsers || 0}</span>
1500                <span className="text-emerald-400 text-sm font-bold">↑ 12%</span>
1501              </div>
1502            </div>
1503
1504            {/* Stat 2 */}
1505            <div className="bg-slate-900/60 border border-slate-800/80 rounded-3xl p-6 relative overflow-hidden glassmorphism">
1506              <span className="text-xs font-bold text-slate-500 uppercase tracking-widest block mb-4">
1507                Response Time
1508              </span>
1509              <div className="flex items-baseline space-x-1">
1510                <span className="text-5xl font-black text-indigo-400 tracking-tight">24</span>
1511                <span className="text-slate-400 text-lg font-bold">ms</span>
1512              </div>
1513            </div>
1514
1515            {/* Stat 3 */}
1516            <div className="bg-slate-900/60 border border-slate-800/80 rounded-3xl p-6 relative overflow-hidden glassmorphism">
1517              <span className="text-xs font-bold text-slate-500 uppercase tracking-widest block mb-4">
1518                Database Status
1519              </span>
1520              <div className="flex items-center space-x-3 mt-2">
1521                <div className="w-3 h-3 bg-emerald-500 rounded-full shadow-[0_0_12px_#10b981]" />
1522                <span className="text-xl font-extrabold text-emerald-400 tracking-wide uppercase">HEALTHY</span>
1523              </div>
1524            </div>
1525          </div>
1526
1527          {/* Main Info Panel */}
1528          <div className="bg-slate-900/40 border border-slate-800/60 rounded-3xl p-8 glassmorphism">
1529            <div className="flex items-center justify-between mb-6">
1530              <div>
1531                <h3 className="text-lg font-bold text-white">Informasi Kernel Server</h3>
1532                <p className="text-xs text-slate-400 mt-0.5">Detail lingkungan runtime eksekusi Axum Anda.</p>
1533              </div>
1534              <span className="text-[10px] font-bold text-indigo-400 bg-indigo-500/10 border border-indigo-500/20 px-3 py-1 rounded-full uppercase tracking-wider">
1535                v2026.1
1536              </span>
1537            </div>
1538
1539            <div className="bg-slate-950 border border-slate-800/50 rounded-2xl p-6 font-mono text-xs text-emerald-400 leading-relaxed shadow-inner">
1540              <div className="text-slate-600 mb-2">// RustBasic SPA Kernel Logs</div>
1541              <div>[OK] Compiled with Axum 0.8.2</div>
1542              <div>[OK] Database Pool: Sea-ORM Connection Established</div>
1543              <div>[OK] Modern SPA Routing: Powered by Inertia.js Bridge</div>
1544              <div>[OK] Single-Binary Mode: Compile-time embedding enabled</div>
1545              <div>[OK] Workers: 8 logical threads spawned on CPU cores</div>
1546            </div>
1547          </div>
1548        </div>
1549      </main>
1550    </div>
1551  );
1552}
1553"##;
1554        fs::write(dashboard_view, dashboard_template).ok();
1555    }
1556    
1557    // 6. Create Dashboard Controller in Rust
1558    let dashboard_controller_path = "src/app/http/controllers/dashboard_controller.rs";
1559    if !std::path::Path::new(dashboard_controller_path).exists() {
1560        let dashboard_template = r#"use crate::app::inertia::inertia;
1561use crate::app::models::users;
1562use rustbasic_core::requests::Request;
1563use rustbasic_core::server::AppState;
1564use rustbasic_core::axum::{response::Response, extract::State};
1565use rustbasic_core::sea_orm::{EntityTrait, PaginatorTrait};
1566use rustbasic_core::serde_json::json;
1567
1568pub struct DashboardController;
1569
1570impl DashboardController {
1571    pub async fn index(State(state): State<AppState>, req: Request) -> Response {
1572        let user_id = req.session.get::<i32>("user_id").unwrap_or(0);
1573        let user = users::Entity::find_by_id(user_id).one(&state.db).await.ok().flatten();
1574        let total_users = users::Entity::find().count(&state.db).await.unwrap_or(0);
1575
1576        inertia(&req, "Dashboard", json!({
1577            "title": "Dashboard",
1578            "userName": user.as_ref().map(|u| u.name.clone()).unwrap_or("Guest".to_string()),
1579            "userEmail": user.as_ref().map(|u| u.email.clone()).unwrap_or_default(),
1580            "totalUsers": total_users,
1581        }))
1582    }
1583}
1584"#;
1585        fs::write(dashboard_controller_path, dashboard_template).ok();
1586        println!("   {} {}", "✅ Created:".green(), dashboard_controller_path.cyan());
1587    }
1588    update_controller_mod_rs("dashboard_controller");
1589
1590    println!("   {} Folder src/resources/js/Pages/Auth dan Dashboard siap.", "✅ Views:".green());
1591
1592    // 7. Update Welcome.jsx
1593    let welcome_path = "src/resources/js/Pages/Welcome.jsx";
1594    if let Ok(content) = fs::read_to_string(welcome_path)
1595        && content.contains("Backend Online") && !content.contains("auth_installed ?") {
1596            let target = r#"          <div className="flex items-center gap-4">
1597            <span className="inline-flex items-center gap-1.5 px-3 h-8 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
1598              <span className="w-2 h-2 rounded-full bg-emerald-400" style={{ boxShadow: "0 0 10px #34d399" }} />
1599              Backend Online
1600            </span>
1601          </div>"#;
1602
1603            let replacement = r#"          <div className="flex items-center gap-4">
1604            <span className="inline-flex items-center gap-1.5 px-3 h-8 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 mr-2">
1605              <span className="w-2 h-2 rounded-full bg-emerald-400" style={{ boxShadow: "0 0 10px #34d399" }} />
1606              Backend Online
1607            </span>
1608            {auth_installed ? (
1609              <Link 
1610                href="/dashboard" 
1611                className="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-sm font-bold text-white transition-all duration-300"
1612                style={{ textDecoration: 'none' }}
1613              >
1614                Dashboard
1615              </Link>
1616            ) : (
1617              <div className="flex gap-2">
1618                <Link 
1619                  href="/login" 
1620                  className="px-4 py-2 rounded-lg border border-white/10 text-sm font-bold hover:bg-white/5 transition-all duration-300 text-gray-300 hover:text-white"
1621                  style={{ textDecoration: 'none' }}
1622                >
1623                  Masuk
1624                </Link>
1625                <Link 
1626                  href="/register" 
1627                  className="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-sm font-bold text-white transition-all duration-300"
1628                  style={{ textDecoration: 'none' }}
1629                >
1630                  Daftar
1631                </Link>
1632              </div>
1633            )}
1634          </div>"#;
1635
1636            let updated = content.replace(target, replacement);
1637            fs::write(welcome_path, updated).ok();
1638            println!("   {} {}", "📝 Updated:".blue(), welcome_path.cyan());
1639        }
1640
1641    println!("\n{}", "✨ Authentication scaffolded successfully!".green().bold());
1642    println!("{}", "Jalankan 'cargo rustbasic route:list' untuk melihat rute baru.".dimmed());
1643}
1644
1645pub async fn remove_auth() {
1646    println!("\n{}", "🗑️  Removing Authentication Scaffold...".red().bold());
1647
1648    // 1. Delete src/routes/auth.rs
1649    let auth_route_path = "src/routes/auth.rs";
1650    if std::path::Path::new(auth_route_path).exists() {
1651        fs::remove_file(auth_route_path).ok();
1652        println!("   {} {}", "✅ Deleted:".green(), auth_route_path.cyan());
1653    }
1654
1655    // 2. Update src/routes/mod.rs
1656    let routes_mod_path = "src/routes/mod.rs";
1657    if let Ok(mut content) = fs::read_to_string(routes_mod_path)
1658        && content.contains("pub mod auth;") {
1659            content = content.replace("pub mod auth;\n", "");
1660            fs::write(routes_mod_path, content).ok();
1661            println!("   {} {}", "📝 Updated:".blue(), routes_mod_path.cyan());
1662        }
1663
1664    // 3. Update src/routes/web.rs
1665    let web_route_path = "src/routes/web.rs";
1666    if let Ok(mut content) = fs::read_to_string(web_route_path) {
1667        let mut changed = false;
1668        
1669        // Remove imports
1670        if content.contains("use rustbasic_core::axum::{Router, routing::{get, post}, middleware::from_fn};") {
1671            content = content.replace("use rustbasic_core::axum::{Router, routing::{get, post}, middleware::from_fn};", "use rustbasic_core::axum::{Router, routing::get};");
1672            changed = true;
1673        }
1674        
1675        let imports_to_remove = [
1676            "use crate::app::http::controllers::{auth, dashboard_controller};\n",
1677            "use crate::app::http::middleware::auth::auth_middleware;\n",
1678            "use rustbasic_core::server::AppState;\n",
1679            "use crate::routes::auth as auth_routes;\n",
1680            "use crate::app::http::controllers::{auth, dashboard_controller};",
1681            "use crate::app::http::middleware::auth::auth_middleware;",
1682            "use crate::routes::auth as auth_routes;",
1683        ];
1684        
1685        for imp in imports_to_remove {
1686            if content.contains(imp) {
1687                content = content.replace(imp, "");
1688                changed = true;
1689            }
1690        }
1691        
1692        // Re-add server::AppState if it was removed
1693        if !content.contains("use rustbasic_core::server::AppState;") {
1694            content = content.replace("use rustbasic_core::axum::{Router, routing::get};", "use rustbasic_core::axum::{Router, routing::get};\nuse rustbasic_core::server::AppState;");
1695        }
1696
1697        // Remove auth_protected_routes logic and restore basic Router
1698        if content.contains("let auth_protected_routes = Router::new()") {
1699            let re = Regex::new(r##"(?s)\s*let auth_protected_routes = Router::new\(\).*?\.layer\(from_fn\(auth_middleware\)\);\s*"##).unwrap();
1700            content = re.replace(&content, "\n").to_string();
1701            
1702            content = content.replace(".merge(auth_routes::router())", "");
1703            content = content.replace(".merge(auth_protected_routes)", "");
1704            
1705            // Restore clean Router::new()
1706            let clean_router = r#"    Router::new()
1707        .route("/", get(welcome_controller::index))
1708        .route("/about", get(welcome_controller::about))
1709        .route("/dev", get(welcome_controller::dev_info))"#;
1710            
1711            let router_re = Regex::new(r##"(?s)Router::new\(\).*?\.route\(\s*\"/dev\"\s*,\s*get\(welcome_controller::dev_info\)\s*\)"##).unwrap();
1712            content = router_re.replace(&content, clean_router).to_string();
1713            
1714            // Final cleanup of multiple newlines
1715            let multi_newline_re = Regex::new(r#"\n{3,}"#).unwrap();
1716            content = multi_newline_re.replace_all(&content, "\n\n").to_string();
1717            
1718            changed = true;
1719        }
1720
1721        if changed {
1722            fs::write(web_route_path, content).ok();
1723            println!("   {} {}", "📝 Updated:".blue(), web_route_path.cyan());
1724        }
1725    }
1726
1727    // 4. Delete Controllers
1728    let auth_controller_dir = "src/app/http/controllers/auth";
1729    if std::path::Path::new(auth_controller_dir).exists() {
1730        fs::remove_dir_all(auth_controller_dir).ok();
1731        println!("   {} {}", "✅ Deleted:".green(), auth_controller_dir.cyan());
1732    }
1733
1734    // 4.1 Delete Password Resets Migration & Model
1735    if let Ok(entries) = std::fs::read_dir("database/migrations") {
1736        for entry in entries.flatten() {
1737            if let Some(name) = entry.file_name().to_str()
1738                && name.ends_with("_create_password_resets_table.rs") {
1739                    let path = entry.path();
1740                    fs::remove_file(&path).ok();
1741                    println!("   {} {}", "✅ Deleted:".green(), path.display().to_string().cyan());
1742                }
1743        }
1744    }
1745    
1746    let model_path = "src/app/models/password_resets.rs";
1747    if std::path::Path::new(model_path).exists() {
1748        fs::remove_file(model_path).ok();
1749        println!("   {} {}", "✅ Deleted:".green(), model_path.cyan());
1750    }
1751
1752    // 5. Delete Components, React Pages/Auth & Dashboard.jsx
1753    let components_dir = "src/resources/js/Components";
1754    let toast_view = "src/resources/js/Components/Toast.jsx";
1755    if std::path::Path::new(toast_view).exists() {
1756        fs::remove_file(toast_view).ok();
1757    }
1758    let alert_banner_view = "src/resources/js/Components/AlertBanner.jsx";
1759    if std::path::Path::new(alert_banner_view).exists() {
1760        fs::remove_file(alert_banner_view).ok();
1761    }
1762    let form_input_view = "src/resources/js/Components/FormInput.jsx";
1763    if std::path::Path::new(form_input_view).exists() {
1764        fs::remove_file(form_input_view).ok();
1765    }
1766    if std::path::Path::new(components_dir).exists()
1767        && let Ok(entries) = std::fs::read_dir(components_dir)
1768            && entries.count() == 0 {
1769                fs::remove_dir(components_dir).ok();
1770            }
1771
1772    let auth_page_dir = "src/resources/js/Pages/Auth";
1773    if std::path::Path::new(auth_page_dir).exists() {
1774        fs::remove_dir_all(auth_page_dir).ok();
1775        println!("   {} {}", "✅ Deleted:".green(), auth_page_dir.cyan());
1776    }
1777
1778    let dashboard_page = "src/resources/js/Pages/Dashboard.jsx";
1779    if std::path::Path::new(dashboard_page).exists() {
1780        fs::remove_file(dashboard_page).ok();
1781        println!("   {} {}", "✅ Deleted:".green(), dashboard_page.cyan());
1782    }
1783
1784    // 5.1 Delete Auth Middleware
1785    let auth_middleware_path = "src/app/http/middleware/auth.rs";
1786    if std::path::Path::new(auth_middleware_path).exists() {
1787        fs::remove_file(auth_middleware_path).ok();
1788        println!("   {} {}", "✅ Deleted:".green(), auth_middleware_path.cyan());
1789    }
1790
1791    let middleware_mod_path = "src/app/http/middleware/mod.rs";
1792    if let Ok(mut content) = fs::read_to_string(middleware_mod_path)
1793        && content.contains("pub mod auth;") {
1794            content = content.replace("pub mod auth;\n", "");
1795            fs::write(middleware_mod_path, content).ok();
1796            println!("   {} {}", "📝 Updated:".blue(), middleware_mod_path.cyan());
1797        }
1798
1799    // 6. Delete Dashboard Controller
1800    let dashboard_path = "src/app/http/controllers/dashboard_controller.rs";
1801    if std::path::Path::new(dashboard_path).exists() {
1802        fs::remove_file(dashboard_path).ok();
1803        println!("   {} {}", "✅ Deleted:".green(), dashboard_path.cyan());
1804    }
1805
1806    // 7. Update src/app/http/controllers/mod.rs
1807    let controllers_mod_path = "src/app/http/controllers/mod.rs";
1808    if let Ok(mut content) = fs::read_to_string(controllers_mod_path) {
1809        let mut changed = false;
1810        if content.contains("pub mod auth;") {
1811            content = content.replace("pub mod auth;\n", "");
1812            changed = true;
1813        }
1814        if content.contains("pub mod dashboard_controller;") {
1815            content = content.replace("pub mod dashboard_controller;\n", "");
1816            changed = true;
1817        }
1818        if changed {
1819            fs::write(controllers_mod_path, content).ok();
1820            println!("   {} {}", "📝 Updated:".blue(), controllers_mod_path.cyan());
1821        }
1822    }
1823
1824    // 7.1 Update src/app/models/mod.rs
1825    let models_mod_path = "src/app/models/mod.rs";
1826    if let Ok(mut content) = fs::read_to_string(models_mod_path) {
1827        let mut changed = false;
1828        if content.contains("pub mod password_resets;") {
1829            content = content.replace("pub mod password_resets;\n", "");
1830            content = content.replace("pub mod password_resets;", "");
1831            changed = true;
1832        }
1833        if changed {
1834            fs::write(models_mod_path, content).ok();
1835            println!("   {} {}", "📝 Updated:".blue(), models_mod_path.cyan());
1836        }
1837    }
1838
1839    // 7.2 Update database/migrations/mod.rs
1840    let migration_mod_path = "database/migrations/mod.rs";
1841    if let Ok(content) = fs::read_to_string(migration_mod_path) {
1842        let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
1843        let mut changed = false;
1844        
1845        // Remove the mod line
1846        lines.retain(|line| {
1847            if line.contains("_create_password_resets_table;") || (line.contains("Box::new(") && line.contains("_create_password_resets_table::Migration")) {
1848                changed = true;
1849                false
1850            } else {
1851                true
1852            }
1853        });
1854
1855        if changed {
1856            fs::write(migration_mod_path, lines.join("\n")).ok();
1857            println!("   {} {}", "📝 Updated:".blue(), migration_mod_path.cyan());
1858        }
1859    }
1860
1861    // 7.3 Restore Welcome.jsx
1862    let welcome_path = "src/resources/js/Pages/Welcome.jsx";
1863    if let Ok(content) = fs::read_to_string(welcome_path)
1864        && content.contains("auth_installed ?") {
1865            let target = r#"          <div className="flex items-center gap-4">
1866            <span className="inline-flex items-center gap-1.5 px-3 h-8 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 mr-2">
1867              <span className="w-2 h-2 rounded-full bg-emerald-400" style={{ boxShadow: "0 0 10px #34d399" }} />
1868              Backend Online
1869            </span>
1870            {auth_installed ? (
1871              <Link 
1872                href="/dashboard" 
1873                className="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-sm font-bold text-white transition-all duration-300"
1874                style={{ textDecoration: 'none' }}
1875              >
1876                Dashboard
1877              </Link>
1878            ) : (
1879              <div className="flex gap-2">
1880                <Link 
1881                  href="/login" 
1882                  className="px-4 py-2 rounded-lg border border-white/10 text-sm font-bold hover:bg-white/5 transition-all duration-300 text-gray-300 hover:text-white"
1883                  style={{ textDecoration: 'none' }}
1884                >
1885                  Masuk
1886                </Link>
1887                <Link 
1888                  href="/register" 
1889                  className="px-4 py-2 rounded-lg bg-indigo-600 hover:bg-indigo-500 text-sm font-bold text-white transition-all duration-300"
1890                  style={{ textDecoration: 'none' }}
1891                >
1892                  Daftar
1893                </Link>
1894              </div>
1895            )}
1896          </div>"#;
1897
1898            let replacement = r#"          <div className="flex items-center gap-4">
1899            <span className="inline-flex items-center gap-1.5 px-3 h-8 rounded-full text-xs font-semibold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20">
1900              <span className="w-2 h-2 rounded-full bg-emerald-400" style={{ boxShadow: "0 0 10px #34d399" }} />
1901              Backend Online
1902            </span>
1903          </div>"#;
1904
1905            let updated = content.replace(target, replacement);
1906            fs::write(welcome_path, updated).ok();
1907            println!("   {} {}", "📝 Restored:".blue(), welcome_path.cyan());
1908        }
1909
1910    // 7.4 Delete Migration Record from Database
1911    println!("   {} {}", "⏳".blue(), "Cleaning up migration records from database...".dimmed());
1912    let db_connection = std::env::var("DB_CONNECTION").unwrap_or_else(|_| "sqlite".to_string());
1913    let db_host = std::env::var("DB_HOST").unwrap_or_else(|_| "127.0.0.1".to_string());
1914    let db_port = std::env::var("DB_PORT").unwrap_or_else(|_| "3306".to_string());
1915    let db_database = std::env::var("DB_DATABASE").unwrap_or_else(|_| "rustbasic".to_string());
1916    let db_username = std::env::var("DB_USERNAME").unwrap_or_else(|_| "root".to_string());
1917    let db_password = std::env::var("DB_PASSWORD").unwrap_or_default();
1918
1919    let db_url = if db_connection == "mysql" {
1920        format!(
1921            "mysql://{}:{}@{}:{}/{}",
1922            db_username, db_password, db_host, db_port, db_database
1923        )
1924    } else {
1925        format!("sqlite:database/{}.sqlite?mode=rwc", db_database)
1926    };
1927
1928    if let Ok(db) = sea_orm::Database::connect(db_url).await {
1929        use sea_orm::ConnectionTrait;
1930        let table_name = if db_connection == "mysql" { "sea_orm_migrations" } else { "seaql_migrations" };
1931        let sql = format!("DELETE FROM {} WHERE version LIKE '%_create_password_resets_table'", table_name);
1932        let db_backend = if db_connection == "mysql" { sea_orm::DbBackend::MySql } else { sea_orm::DbBackend::Sqlite };
1933        let _ = db.execute(sea_orm::Statement::from_string(db_backend, sql)).await;
1934        println!("   {} {}", "✅ Cleaned:".green(), "Database migration records removed.".cyan());
1935    }
1936
1937    // Clean up validator and sea-orm-migration from Cargo.toml
1938    if let Ok(mut content) = fs::read_to_string("Cargo.toml") {
1939        let mut changed = false;
1940        
1941        let validator_lines = [
1942            "validator = { version = \"0.20\", features = [\"derive\"] }\n",
1943            "validator = { version = \"0.20\", features = [\"derive\"] }",
1944        ];
1945        for line in &validator_lines {
1946            if content.contains(line) {
1947                content = content.replace(line, "");
1948                changed = true;
1949            }
1950        }
1951        
1952        let migration_lines = [
1953            "sea-orm-migration = { version = \"1.1\", features = [\"runtime-tokio-rustls\", \"sqlx-sqlite\", \"sqlx-mysql\"], default-features = false }\n",
1954            "sea-orm-migration = { version = \"1.1\", features = [\"runtime-tokio-rustls\", \"sqlx-sqlite\", \"sqlx-mysql\"], default-features = false }",
1955        ];
1956        for line in &migration_lines {
1957            if content.contains(line) {
1958                content = content.replace(line, "");
1959                changed = true;
1960            }
1961        }
1962        
1963        if changed {
1964            fs::write("Cargo.toml", content).ok();
1965            println!("   {} {}", "📝 Updated:".blue(), "Cargo.toml dependencies cleaned".cyan());
1966        }
1967    }
1968
1969    println!("\n{}", "✨ Authentication removed successfully!".green().bold());
1970}